이번엔 join에 대해 알아보자. join은 1:1 맵핑, 1:m, m:n 아마도 이렇게 구성이 될것이다.
여기서는 1:1에 대해 알아보자.
객체는 person과 personDetail 두가지가 있다. 그럼 코드를 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | package com.zest.hibernate.chapter6.onetoonemapping; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToOne; /** * * one to one * ex) person data table * person id : 1 *person name: aaaa *pdetail_fk :32768 이넘 * *person_detail *detailId_pk : 32768 이넘 *income:123456786 *job : adklja *zipcode : 20815 * *위에 '이넘' 두개가 1:1로 맵핑. */ @Entity public class Person { @Id @GeneratedValue private int personId; private String personName; /** * CascadeType의 종류에는 다음과 같은 것들이 있다. CascadeType.RESIST – 엔티티를 생성하고, 연관 엔티티를 추가하였을 때 persist() 를 수행하면 연관 엔티티도 함께 persist()가 수행된다. 만약 연관 엔티티가 DB에 등록된 키값을 가지고 있다면 detached entity passed to persist Exception이 발생한다. CascadeType.MERGE – 트랜잭션이 종료되고 detach 상태에서 연관 엔티티를 추가하거나 변경된 이후에 부모 엔티티가 merge()를 수행하게 되면 변경사항이 적용된다.(연관 엔티티의 추가 및 수정 모두 반영됨) CascadeType.REMOVE – 삭제 시 연관된 엔티티도 같이 삭제됨 CascadeType.DETACH – 부모 엔티티가 detach()를 수행하게 되면, 연관된 엔티티도 detach() 상태가 되어 변경사항이 반영되지 않는다. CascadeType.ALL – 모든 Cascade 적용 fetch 의 순위 두 개 있다 fetch=FetchType.EAGER: 실행 문 바로 꺼냈다 fetch=FetchType.LAZY: 쓸 때 비로소 꺼냈다 */ @OneToOne(cascade=CascadeType.ALL, fetch= FetchType.EAGER) @JoinColumn(name="pDetail_FK") private PersonDetail pDetail; public PersonDetail getpDetail() { return pDetail; } public void setpDetail(PersonDetail pDetail) { this.pDetail = pDetail; } public int getPersonId() { return personId; } public void setPersonId(int personId) { this.personId = personId; } public String getPersonName() { return personName; } public void setPersonName(String personName) { this.personName = personName; } } | cs |
새로운 어노테이션이 등장한다. OneToOne 과 JoinColumn 이 추가 되었다.
OneToOne의 경우 외래키를 만들때 선언하는 어노테이션이다. Cascade속성과 Fetch 속성 두가지를 지원한다. JoinColumn은 외래키가 되는 Column의 실제 Column이름을 지정하는것이다. 그냥 테이블 생성시에는 @Column이라는 놈이랑 비슷하다고 생각하면된다. 그리고 중요한 것은 기본키가 되는 객체에 어노테이션을 선언한다는것이다.
그럼 personDetail을 만들어보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | package com.zest.hibernate.chapter6.onetoonemapping; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity public class PersonDetail { @Id @GeneratedValue @Column(name="detailId_PK") private int personDetailId; private String zipCode; private String job; private double income; public int getPersonDetailId() { return personDetailId; } public void setPersonDetailId(int personDetailId) { this.personDetailId = personDetailId; } public String getZipCode() { return zipCode; } public void setZipCode(String zipCode) { this.zipCode = zipCode; } public String getJob() { return job; } public void setJob(String job) { this.job = job; } public double getIncome() { return income; } public void setIncome(double income) { this.income = income; } } | cs |
기존 테이블을 만들던 코드와 똑같다. 테스트 코드 ㄱㄱ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | package com.zest.hibernate.chapter6.onetoonemapping; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.AnnotationConfiguration; import org.hibernate.tool.hbm2ddl.SchemaExport; import org.junit.Test; import com.zest.hibernate.chapter5.inheritancemapping.Module; import com.zest.hibernate.chapter5.inheritancemapping.Project; import com.zest.hibernate.chapter5.inheritancemapping.Task; public class OneToOneTest { @Test public void oneTest(){ AnnotationConfiguration config = new AnnotationConfiguration(); config.addAnnotatedClass(Person.class); config.addAnnotatedClass(PersonDetail.class); config.configure("hibernate.cfg.xml"); new SchemaExport(config).create(true, true); // 아래 두개의 구문은 객체를 트랜잭션을 컨트롤. SessionFactory factory = config.buildSessionFactory(); Session session = factory.getCurrentSession(); session.beginTransaction(); PersonDetail alexDetail = new PersonDetail(); alexDetail.setZipCode("20815"); alexDetail.setJob("Accountant"); alexDetail.setIncome(67245.56); Person alex = new Person(); alex.setPersonName("alex berry"); alex.setpDetail(alexDetail); session.save(alex); //session.save(alexdetail); 이건 안함. 카스케이드 라서 session.getTransaction().commit(); } } | cs |
결과는 다음과 같다.
1 2 3 | Hibernate: insert into TESTSCHEMA.PersonDetail (income, job, zipCode, detailId_PK) values (?, ?, ?, ?) Hibernate: insert into TESTSCHEMA.Person (pDetail_FK, personName, personId) values (?, ?, ?) | cs |
테이블 구조를 보자. Person의 테이블을 살펴보면 PersonId는 기본키이며 pDetail_FK는 외래키로 생성이 될것이다. 여기까지 OneAndOne 설명을 마친다.
이번엔 상속 관계에 대해 알아보자.
Project, Module, Task 이 3개 객체가 있다.
최상위 객체는 Project이며, 그 자식으로 Module 이 있으며, Module의 자식으로 Task가 있다.
먼저 소스를 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | package com.zest.hibernate.chapter5.inheritancemapping; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; @Entity //single table 실행시 stategy가 없는것과 동일한 결과를 볼수있다. @Inheritance(strategy=InheritanceType.SINGLE_TABLE) //각 class마다 table이 생성되며, 하위 class들은 pk키를 기준으로 테이블이 생성된다. //또, 최상위 클래스는 pk와 하위 컬럼만 존재한다. //@Inheritance(strategy=InheritanceType.JOINED) //class에 맞는 컬럼 값만 저장하며, pk는 최상위 클래스 값으로 중복되지 않은 값으로 저장된다. //@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) public class Project { @Id @GeneratedValue private int projectId; private String projectName; public int getProjectId() { return projectId; } public void setProjectId(int projectId) { this.projectId = projectId; } public String getProjectName() { return projectName; } public void setProjectName(String projectName) { this.projectName = projectName; } } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | package com.zest.hibernate.chapter5.inheritancemapping; import javax.persistence.Entity; @Entity public class Module extends Project{ private String moduleName; public String getModuleName() { return moduleName; } public void setModuleName(String moduleName) { this.moduleName = moduleName; } } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package com.zest.hibernate.chapter5.inheritancemapping; import javax.persistence.Entity; @Entity public class Task extends Module{ private String taskName; public String getTaskName() { return taskName; } public void setTaskName(String taskName) { this.taskName = taskName; } } | cs |
먼저 상속에 관한 옵션은 3가지 어노테이션이 존재한다.
@Inheritance(strategy=InheritanceType.SINGLE_TABLE) - 디폴트 선언이며, 하위 자식의 값들은 무시되며, 최상위 클래스의 테이블에만 정보를 넣는다.
@Inheritance(strategy=InheritanceType.JOINED) - 이름에서 알듯이 자식들은 부모와 fk로 엮이며, 부모는 자식의 값을 가지고 있고, 자식은 자신의 값을 가지고 있는다. 결국 부모는 전체의 데이터를 가지고 있고 자식은 fk로 본인의 값들을 가지고 있는다.
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) - 부모 자식 포함해서 중복되지 않는 값을 서로간에 가지고 있는다.
아마도 제일 많이 쓰이는건 2번이 아닐까 싶다. 그럼 테스트 코드를 보자. 먼저 @Inheritance(strategy=InheritanceType.SINGLE_TABLE) 일 경우
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | package com.zest.hibernate.chapter5.inheritancemapping; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.AnnotationConfiguration; import org.hibernate.tool.hbm2ddl.SchemaExport; import org.junit.Test; public class InheritaceMappingTest { @Test public void defaultRunTableTest(){ AnnotationConfiguration config = new AnnotationConfiguration(); config.addAnnotatedClass(Project.class); config.addAnnotatedClass(Module.class); config.addAnnotatedClass(Task.class); config.configure("hibernate.cfg.xml"); new SchemaExport(config).create(true, true); // 아래 두개의 구문은 객체를 트랜잭션을 컨트롤. SessionFactory factory = config.buildSessionFactory(); Session session = factory.getCurrentSession(); session.beginTransaction(); Project p = new Project(); p.setProjectName("hibernate lessons"); Module m = new Module(); m.setProjectName("spring lessons"); m.setModuleName("aop"); Task t = new Task(); t.setProjectName("java lessons"); t.setModuleName("collections"); t.setTaskName("arraylist"); session.save(p); session.save(m); session.save(t); session.getTransaction().commit(); } } | cs |
1 2 3 4 | Hibernate: insert into TESTSCHEMA.Project (projectName, DTYPE, projectId) values (?, 'Project', ?) Hibernate: insert into TESTSCHEMA.Project (projectName, moduleName, DTYPE, projectId) values (?, ?, 'Module', ?) Hibernate: insert into TESTSCHEMA.Project (projectName, moduleName, taskName, DTYPE, projectId) values (?, ?, ?, 'Task', ?) | cs |
위의 형태로 로그가 나올 것이다. 결과를 보면 project 말고는 다른 테이블은 생성이 안될 것이다.
1 2 3 4 | drop table testschema.task; drop table testschema.module; drop table testschema.project; | cs |
다시 테이블을 드랍 시키고 @Inheritance(strategy=InheritanceType.JOINED) 이 어노테이션을 적용해보자.
1 2 3 4 5 6 7 | Hibernate: insert into TESTSCHEMA.Project (projectName, projectId) values (?, ?) Hibernate: insert into TESTSCHEMA.Project (projectName, projectId) values (?, ?) Hibernate: insert into TESTSCHEMA.Module (moduleName, projectId) values (?, ?) Hibernate: insert into TESTSCHEMA.Project (projectName, projectId) values (?, ?) Hibernate: insert into TESTSCHEMA.Module (moduleName, projectId) values (?, ?) Hibernate: insert into TESTSCHEMA.Task (taskName, projectId) values (?, ?) | cs |
Project에는 결과가 총 3개가 생성이 된다. Module에 두개의 row가 생성되며, 마지막 task에는 1개의 row가 생성 될것이다. 그리고 테이블의 구조를 살펴보면 module.projectId , task.projectId는 pk,fk로 되어있다.
마지막 @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)를 해보자. 먼저 테이블을 drop후 위에 선언한 project 객체의 주석을 변경한다. 그리고 테스트 ㄱㄱㄱㄱ
1 2 3 | Hibernate: insert into TESTSCHEMA.Project (projectName, projectId) values (?, ?) Hibernate: insert into TESTSCHEMA.Module (projectName, moduleName, projectId) values (?, ?, ?) Hibernate: insert into TESTSCHEMA.Task (projectName, moduleName, taskName, projectId) values (?, ?, ?, ?) | cs |
자신의 데이터만 row에 생길 것이다.
소스는 https://github.com/zest133/hibernateTest.git 여기에
이번엔 복합키에 대해 알아보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | package com.zest.hibernate.chapter4; import javax.persistence.Entity; import javax.persistence.Id; @Entity public class Accounts { private int userId; private int accountId; // @Id // //만약 변수에 annotaion을 할꺼면 private는 안된다. 다른 객체이므로. getxxx 에 annotaion을 붙이면 된다. // CompoundKey compoundKey; private int accountBalance; // public CompoundKey getCompoundKey() { // return compoundKey; // } // public void setCompoundKey(CompoundKey compoundKey) { // this.compoundKey = compoundKey; // } public int getAccountBalance() { return accountBalance; } public void setAccountBalance(int accountBalance) { this.accountBalance = accountBalance; } } | cs |
userId와 accountId에 복합키를 걸면 어떻게 할까?
자 새로운 객체를 한개더 생성하자. 클래스 이름은 CompundKey이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | package com.zest.hibernate.chapter4; import java.io.Serializable; import javax.persistence.Embeddable; //필히 Serializable 해줘야함. @Embeddable public class CompoundKey implements Serializable{ private int userId; private int accountId; public CompoundKey(int userId, int accountId){ this.accountId = accountId; this.userId = userId; } public int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; } public int getAccountId() { return accountId; } public void setAccountId(int accountId) { this.accountId = accountId; } } | cs |
여기서도 @Embeddable 이라는 어노테이션을 사용한다. 중요한 점은 필히
Serializable을 해야하는 점이다. 그리고 Accounts 클래스를 수정하자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | package com.zest.hibernate.chapter4; import javax.persistence.Entity; import javax.persistence.Id; @Entity public class Accounts { //compound key로 옮겼으니 주석으로 막는다. // private int userId; // private int accountId; @Id //만약 변수에 annotaion을 할꺼면 private는 안된다. 다른 객체이므로. getxxx 에 annotaion을 붙이면 된다. CompoundKey compoundKey; private int accountBalance; public CompoundKey getCompoundKey() { return compoundKey; } public void setCompoundKey(CompoundKey compoundKey) { this.compoundKey = compoundKey; } public int getAccountBalance() { return accountBalance; } public void setAccountBalance(int accountBalance) { this.accountBalance = accountBalance; } } | cs |
CompoundKey라는 객체에 @Id 붙여 기본키를 만들었다. 다른 예제와 틀리게 private가 없을 것이다. 외부에 있는 변수를 끌어와서 private를 쓰면 에러가 난다. 이때 getxxx의 함수에 @Id를 붙이거나 또는 private 이상의 권한을 줘야만 된다.
그럼 테스트 코드로...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | package com.zest.hibernate.chapter4; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.AnnotationConfiguration; import org.hibernate.tool.hbm2ddl.SchemaExport; import org.junit.Test; public class AccountTest { @Test public void account1() { AnnotationConfiguration config = new AnnotationConfiguration(); config.addAnnotatedClass(Accounts.class); config.configure("hibernate.cfg.xml"); new SchemaExport(config).create(true, true); // 아래 두개의 구문은 객체를 트랜잭션을 컨트롤. SessionFactory factory = config.buildSessionFactory(); Session session = factory.getCurrentSession(); session.beginTransaction(); CompoundKey key1 = new CompoundKey(100, 10001); Accounts savings = new Accounts(); savings.setCompoundKey(key1); savings.setAccountBalance(8500); CompoundKey key2 = new CompoundKey(100, 20001); Accounts checking = new Accounts(); checking.setCompoundKey(key2); checking.setAccountBalance(2500); session.save(savings); session.save(checking); session.getTransaction().commit(); } } | cs |
index를 확인하면 userid와 accountid가 복합키로 생성되있을 것이다.