프로그래밍n학습자료(2018~)/Hibernate

[Hibernate] 3. Hibernate(하이버네이트) 테이블 관계 - One To One

단세포소년 2018. 3. 8. 16:52
반응형

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 : 해당 엔티티와 맵핑되는 테이블을 정의한다.
- name :
해당 엔티티와 맵핑되는 테이블의 실제 이름을 입력하면 된다.

 

@Id

@GeneratedValue(strategy=GenerationType.IDENTITY)

@Column(name="PEOPLE_ID",columnDefinition="INT")

private int id;

@id : 이것이 primary key 라고 알려주는 것이다.
@GeneratedValue : 생성되어지는 값이고 Mysql Auto Increment 사용했다면 GenerationType.IDENTITY 지정한다.
@Column :
컬럼에 대해 정의하는 것이다. name 변수와 맵핑되는 컬럼명이다. columnDefinition 컬럼의 타입이다.

 

@OneToOne(fetch=FetchType.LAZY,mappedBy="people", cascade = CascadeType.ALL)

private People_Info people_Info;

@OneToOne : 관계이다.
fetch :
관계된 데이터를 가져오는 시점을 말한다.FetchType.LAZY 필요한 시점 해당 변수를 사용하는 시점에 DB에서 데이터를 가져오는 것이며 FetchType.Eager 사용시점 상관없이 즉시 불러오는 것이다. 가끔씩 사용하는 데이터라면 LAZY, 매번 사용한다면 Eager 사용하면 된다.

 

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
- generator : 생성자 이름을 입력하면 된다.

@GenericGenerator : key 생성자에 대한 정의이다.
- name :
생성자 이름이다. 원하는 이름으로 입력한다.
- strategy="foreign" :  foreign
키라는 것을 알려준다.
-
@Parameter(name="property", value="people")) : value에는 참조되는 관계명(변수명) 입력한다. People_Info 변수 'private People people' 값이다. (value값은 테이블명이 아니다. 관계가 정의된 변수명이다.)

 

 

 

 

 

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;

 


반응형