OneToOne 관계
CREATE TABLE `people` ( `PEOPLE_ID` int(11) NOT NULL AUTO_INCREMENT, `PEOPLE_NAME` varchar(45) NOT NULL, PRIMARY KEY (`PEOPLE_ID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `people_info` ( `PEOPLE_INFO_ID` int(11) NOT NULL, `PEOPLE_INFO_AGE` int(11) DEFAULT NULL, `PEOPLE_INFO_BIRTHDAY` datetime DEFAULT NULL, PRIMARY KEY (`PEOPLE_INFO_ID`), UNIQUE KEY `PEOPLE_INFO_ID_UNIQUE` (`PEOPLE_INFO_ID`), CONSTRAINT `fk_people_info` FOREIGN KEY (`PEOPLE_INFO_ID`) REFERENCES `people` (`PEOPLE_ID`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
people 과 people_info는 Primary Key가 동일한 OneToOne 관계이다.
people_info의 PEOPLE_INFO_ID는 people의 PEOPLE_ID를 참조한다.
Primarykey join, Bi-directional(양방향)
위의 예제를 통해 Bi-directional 즉 양방향 맵핑을 구현할 것이다.
양방향의 특징은 한쪽에만 맵핑을 해주면 다른 쪽에서는 직접 맵핑 할 필요 없이 맵핑 이름만 가져오면 된다.
예를 들어 people_info 에 people과의 관계를 정의했다면, people에서는 people_info에 정의된 관계 이름만 있으면 된다. 왜냐하면 한방향을 설정하면 그 반대가 역방향이기 때문이다.
아래의 소스 코드를 보면 멤버변수명과 테이블의 컬럼명을 다르게 했다. 왜냐면 어노테이션에서 쓰이는 이름이 컬럼명인지 멤버변수명인지 정확히 구분하기 위해서 이다. 나는 보통 멤버변수명과 컬럼명을 동일하게 한다.
맵핑
People.java 코드
package org.onecellboy.db.hibernate.table;
import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToOne; import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.Table;
@Entity @Table(name="people") public class People {
@Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(name="PEOPLE_ID",columnDefinition="INT") private int id;
@Column(name="PEOPLE_NAME",columnDefinition="VARCHAR(45)") private String name;
@OneToOne(fetch=FetchType.LAZY,mappedBy="people", cascade = CascadeType.ALL) private People_Info people_Info;
public People_Info getPeople_Info() { return people_Info; }
public void setPeople_Info(People_Info people_Info) { this.people_Info = people_Info; }
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
} |
설명
@Entity @Table(name="people") |
@Entity : 엔티티라는 것 @Table : 해당 엔티티와 맵핑되는 테이블을 정의한다. |
@Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(name="PEOPLE_ID",columnDefinition="INT") private int id; |
@id : 이것이 primary
key 라고 알려주는 것이다. |
@OneToOne(fetch=FetchType.LAZY,mappedBy="people", cascade = CascadeType.ALL) private People_Info people_Info; |
@OneToOne : 관계이다.
mappedBy : 양방향 맵핑에서 반대편 엔티티에 정의된 자신으로 향하는 관계 맵핑 변수명이다. 위에서는 People_Info 에 정의된 자신으로의 관계인 변수명이다.
CascadeType : 부모 객체의 상태에 따라 자식 객체를 어찌 처리할 지에 대한 옵션이다. 위에서는 People 이 Insert,Delete 되면 People에 포함된
People_Info도 자동으로 Insert,Delete 되게 하기 위해서 CascadeType.ALL을 설정했다. 이 값은 Hibernate Layer에서 동작한다.( 주의 : Session이 Close() 되기전까지만 동작한다. Session내에서만 사용하라. ) |
People_Info.java
package org.onecellboy.db.hibernate.table;
import java.util.Date;
import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.Parameter;
@Entity @Table(name="people_info") public class People_Info {
@Id @Column(name="PEOPLE_INFO_ID",columnDefinition="INT") @GeneratedValue(generator="SharedPrimaryKeyGenerator") @GenericGenerator(name="SharedPrimaryKeyGenerator",strategy="foreign",parameters = @Parameter(name="property", value="people")) private int info_id;
@Column(name="PEOPLE_INFO_AGE",columnDefinition="INT") private int age;
@Column(name="PEOPLE_INFO_BIRTHDAY",columnDefinition="DATETIME") private Date birthday;
@OneToOne(fetch=FetchType.LAZY) //@PrimaryKeyJoinColumn // 혹은 @JoinColumn(name = "PEOPLE_INFO_ID",referencedColumnName="PEOPLE_ID") private People people;
public People getPeople() { return people; }
public void setPeople(People people) { this.people = people; }
public int getInfo_id() { return info_id; }
public void setInfo_id(int info_id) { this.info_id = info_id; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public Date getBirthday() { return birthday; }
public void setBirthday(Date birthday) { this.birthday = birthday; }
} |
설명
@Id @Column(name="PEOPLE_INFO_ID",columnDefinition="INT") @GeneratedValue(generator="SharedPrimaryKeyGenerator") @GenericGenerator(name="SharedPrimaryKeyGenerator",strategy="foreign",parameters =@Parameter(name="property", value="people")) private int info_id; |
위에서는 People_info의 primary key가 People의 Primary Key의 Foreign 키이므로 이것을 정의해야 Insert 시 자동으로 People의 Primary key로 People_Info의 Primary Key를 설정한다.
@GeneratedValue |
hibernate.cfg.xml 에 mapping 클래스를 지정한다.
생략... <mapping class="org.onecellboy.db.hibernate.table.People"/> <mapping class="org.onecellboy.db.hibernate.table.People_Info"/> 생략... |
테스트
테스트 코드
package org.onecellboy.db.hibernate;
import static org.junit.Assert.*;
import java.io.File; import java.util.Date;
import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; import org.onecellboy.db.hibernate.table.People; import org.onecellboy.db.hibernate.table.People_Info;
/* * Bi-directional OneToOne 양방향 1:1 관계 * */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class OneToOneBi {
static SessionFactory sessionFactory = null; static StandardServiceRegistry registry = null;
static int select_id = 0;
@BeforeClass public static void setUp() { registry = new StandardServiceRegistryBuilder().configure(new File("./conf/hibernate/hibernate.cfg.xml")) .build(); sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory();
}
@AfterClass public static void setDown() { sessionFactory.close(); }
@After public void testAfter() { System.out.println(); System.out.println(); }
@Test public void test01_Insert() { System.out.println("======Insert TEST======"); Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
System.out.println("---People Insert---");
People people = new People(); people.setName("소년");
System.out.println("*insert 전 people id : "+people.getId());
session.save(people);
System.out.println("*insert 후 people id : "+people.getId());
System.out.println(); System.out.println("---People Insert 시 자동 People_Info Insert---");
people = new People(); people.setName("소년2");
People_Info people_Info = new People_Info(); people_Info.setAge(12); people_Info.setBirthday(new Date());
System.out.println("*insert 전 people_Info id : "+people_Info.getInfo_id());
people.setPeople_Info(people_Info); people_Info.setPeople(people); session.save(people);
select_id = people.getId();
System.out.println("*insert 후 people_Info id : "+people_Info.getInfo_id());
tx.commit(); session.close();
}
@Test public void test02_select() { System.out.println("======Select TEST======"); Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
People people = session.get(People.class, select_id);
System.out.println("*people.id : "+people.getId()); System.out.println("*people.name : "+people.getName());
People_Info people_Info = people.getPeople_Info();
System.out.println("*people_Info.info_id : "+people_Info.getInfo_id()); System.out.println("*people_Info.age : "+people_Info.getAge()); System.out.println("*people_Info.birthday : "+people_Info.getBirthday());
tx.commit(); session.close(); }
@Test public void test03_delete() { System.out.println("======Delete TEST======"); Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
People people = session.get(People.class, select_id);
session.delete(people);
People_Info people_Info = session.get(People_Info.class, select_id);
if(people_Info==null) { System.out.println("*people_Info == null"); }
tx.commit();
session.close(); }
} |
주의: 부모(people)에 자식(people_info)를 설정( People.setPeople_Info() )하고 자식도 부모를 설정( People_Info.setPeople() ) 즉, 양방향으로 설정해야 부모(people)을 Insert시 자동으로 자식(people_info)도 저장된다. 한방향만 설정하면 되지 않으니 주의하자.
위 코드에서 보면
people.setPeople_Info(people_Info); people_Info.setPeople(people); session.save(people); |
이 부분이 있다.
부모가 자식을 설정, 자식이 부모를 설정하는 부분이다. 근데 실제로는 이렇게 처리하는 것 보다는 부모에서 다 처리하는 것이 좋다.
예를 들어
@Entity @Table(name="people") public class People {
// 생략...
public void setPeople_Info(People_Info people_Info) { if(this.people_Info!=null) { this.people_Info.setPeople(null); } this.people_Info = people_Info;
if(people_Info!=null) { people_Info.setPeople(this); } }
// 생략... } |
위 코드를 사용하면 부모에서 자식만 설정하면 자동으로 자식에 부모를 설정한다. 부모가 주가 되어 자식을 컨트롤하는게 의미상도 더 좋다.
결과
======Insert TEST====== ---People Insert--- *insert 전 people id : 0 Hibernate: insert into people (PEOPLE_NAME) values (?) *insert 후 people id : 42
---People Insert 시 자동 People_Info Insert--- *insert 전 people_Info id : 0 Hibernate: insert into people (PEOPLE_NAME) values (?) *insert 후 people_Info id : 43 Hibernate: insert into people_info (PEOPLE_INFO_AGE, PEOPLE_INFO_BIRTHDAY, PEOPLE_INFO_ID) values (?, ?, ?)
======Select TEST====== Hibernate: select people0_.PEOPLE_ID as PEOPLE_I1_0_0_, people0_.PEOPLE_NAME as PEOPLE_N2_0_0_ from people people0_ where people0_.PEOPLE_ID=? Hibernate: select people_inf0_.PEOPLE_INFO_ID as PEOPLE_I1_1_0_, people_inf0_.PEOPLE_INFO_AGE as PEOPLE_I2_1_0_, people_inf0_.PEOPLE_INFO_BIRTHDAY as PEOPLE_I3_1_0_ from people_info people_inf0_ where people_inf0_.PEOPLE_INFO_ID=? *people.id : 43 *people.name : 소년2 *people_Info.info_id : 43 *people_Info.age : 12 *people_Info.birthday : 2018-01-04 12:15:38.0
======Delete TEST====== Hibernate: select people0_.PEOPLE_ID as PEOPLE_I1_0_0_, people0_.PEOPLE_NAME as PEOPLE_N2_0_0_ from people people0_ where people0_.PEOPLE_ID=? Hibernate: select people_inf0_.PEOPLE_INFO_ID as PEOPLE_I1_1_0_, people_inf0_.PEOPLE_INFO_AGE as PEOPLE_I2_1_0_, people_inf0_.PEOPLE_INFO_BIRTHDAY as PEOPLE_I3_1_0_ from people_info people_inf0_ where people_inf0_.PEOPLE_INFO_ID=? *people_Info == null Hibernate: delete from people_info where PEOPLE_INFO_ID=? Hibernate: delete from people where PEOPLE_ID=? |
hibernate SQL 로그도 출력하였다.
hibernate 설정 파일에서 아래와 같이 적용하면 SQL이 stdout으로 출력된다.
<!-- Echo all executed SQL to stdout --> <property name="show_sql">true</property> <property name="format_sql">true</property> |
결과가 난잡하긴 한데
people 과 people_info를 서로 set하고 people을 save()하면 자동으로 people_info 가 insert된다는 것을 확인할 수 있다.
People을 select 하였을 때 getPeople_Info()를 통해 관계된 people_info를 select 할 수 있다는 것을 확인할 수 있다.
People을 delete 하였을 때 관계된 people_info도 같이 delete 됨을 확인할 수 있다.
Primarykey join, Unidirectional(단방향)
PrimaryKey Join 시 단방향( 부모에서 자식방향 )은 부모 insert시 자동으로 자식이 저장되지 않는다. 기본적으로 OneToOne 단방향에서는 부모 insert 시점에 부모보다 자식을 우선적으로 저장하고 그 다음 부모를 저장한다. 왜냐면 부모에서 자식으로 방향이 흐른다는 것은 부모 컬럼내에 자식을 참조하는 컬럼이 있다고 판단하기 때문이다. 이 때문에 자식이 저장되어야 자식의 키가 존재하고 이를 부모가 자식을 참조하는 컬럼값을 지정할 수 있기 때문이다.
PrimaryKey Join의 경우는 ForgineKey 제약 때문에 자식이 먼저 저장될 수 없는 문제가 있다.
public class People {
@OneToOne(fetch=FetchType.LAZY,mappedBy="people", cascade = CascadeType.ALL) @OneToOne(fetch=FetchType.LAZY) @JoinColumn(name="PEOPLE_ID",referencedColumnName="PEOPLE_INFO_ID") private People_Info people_Info; |
위에 같이 바꾸어 주기만 하면된다.
그냥 일반적인 JoinColumn 이다.
People과 People_Info를 각각 저장해야 된다. 예를 들어 People를 저장하고 얻은 primarykey를 People_Info의 primarykey로 설정하고 저장하는 방식이다.
위의 관계는 select 용도로만 사용하면 된다.
People_Info 도 동일하게 바꾸면 된다.
public class People_Info {
@OneToOne(fetch=FetchType.LAZY,mappedBy="people", cascade = CascadeType.ALL) @OneToOne(fetch=FetchType.LAZY) @JoinColumn(name="PEOPLE_ID",referencedColumnName="PEOPLE_INFO_ID") private People_Info people_Info; |
'프로그래밍n학습자료(2018~) > Hibernate' 카테고리의 다른 글
[Hibernate] 6. Hibernate(하이버네이트) 테이블 관계 - Many To Many (0) | 2018.03.08 |
---|---|
[Hibernate] 5. Hibernate(하이버네이트) 테이블 관계 - One To Many ( 중간 관계 테이블 있을 시) (0) | 2018.03.08 |
[Hibernate] 4. Hibernate(하이버네이트) 테이블 관계 - One To Many ( 중간 관계 테이블 없을 시) (0) | 2018.03.08 |
[Hibernate] 2. Hibernate(하이버네이트) 테이블 관계 맵핑-단방향/양방향 (0) | 2018.03.08 |
[Hibernate]1. Hibernate(하이버네이트) ORM 시작 , 예제 (0) | 2018.03.08 |