잊지 않겠습니다.

Hibernate @OneToOne

Java 2014. 8. 19. 01:50

Hibernate @OneToOne

  • 기본적으로 @OneToOne을 사용하지 않는 것이 좋다. @OneToOne의 경우에는 Lazy Loading에 심각한 문제가 있고, 이는 전체 객체에 대한 어마어마한 로딩을 가지고 오는 결과를 가지고 온다.
  • @SecondaryTable로 해결할수도 있으나, BL에 따라서 생각하는 것이 좋다.

기본적으로 다음 기준을 따른다.

  • parent가 되는 entity를 결정하고, 그 entity가 나중에 insert되는 senerio를 택한다.
  • 여러 table의 집합 정보를 가지게 된다면 그 table은 child로 구성한다.

예시

예를 들어 다음과 같은 BL이 존재한다면 Entity는 다음과 같이 구성되어야지 된다.

  • Book과 Note가 존재하고, 둘의 Summary를 지정한다.
  • Book, Note와 Summary는 @OneToOne 관계를 가지게 된다.

이럴때, Entity code의 구성은 다음과 같다.

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column
    private String title;

    @OneToOne(fetch = FetchType.LAZY, optional = false, cascade = CascadeType.ALL)
    @JoinColumn(name = "bookId", nullable = false, unique = true)
    private Summary summary;
}


@Entity
public class Note {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column
    private String title;

    @OneToOne
    @JoinColumn(name = "noteId")
    private Summary summary;
}

@Entity
public class Summary {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column
    private String context;
}

위 entity로 Book을 추가하는 code는 다음과 같이 구성된다.

@Override
public Book add(String name) {
    SessionFactory sessionFactory = SessionUtils.build();
    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();
    try {
        Book book = new Book();
        book.setTitle(name);

        Summary summary = new Summary();
        summary.setContext("CONTEXT FROM BOOK");
        book.setSummary(summary);
        session.save(book);
        transaction.commit();

        return book;
    } catch(Exception ex) {
        transaction.rollback();
        throw ex;
    } finally {
        session.close();
    }
}

위 코드가 실행되면, 다음과 같은 Query 결과를 보여준다.

Hibernate: insert into Summary (context) values (?)
Hibernate: insert into Book (bookId, title) values (?, ?)
Hibernate: select this_.id as id1_0_0_, this_.bookId as bookId3_0_0_, this_.title as title2_0_0_ from Book this_

DB로 생각하면, 먼저 insert될 정보가 main, parent가 되어야지 되고, child는 나중에 insert가 되어야지 된다고 생각하기 쉽다. 그렇지만, 이는 @OneToMany로 지정된 parent-child 구조에서 이렇게 되는 것이고, @OneToOne의 경우에는 child가 먼저 저장이 되어야지 되는 것을 명심하자. 이는 DB Table의 구조에 지대한 영향을 미치게 된다.

DB 구조

  • @OneToMany의 경우, child에서 parent PK를 갖는 구조가 되어야지 된다.
  • @OneToOne의 경우, parent에서 child PK를 갖는 구조가 되어야지 된다.

DB의 구조는 BL을 따라가기 때문에, 어떤 기준으로 검색을 해야지 되는지에 따라서 Table구조가 바뀐다면 위 원칙만을 기억하고 처리하면 가장 좋을 것 같다.

@OneToOne에서의 Lazy 문제

기본적으로 @OneToOne은 Early Loading을 하게 된다. 그 이유는 null 값이 가능한 OneToOne child를 Proxy화 할 수 없기 때문이다. (null이 아닌 proxy instance를 return하기 때문에 DB값의 null을 표현하는 것이 불가능하다.) 따라서 JPA 구현체는 기본적으로 @OneToOne에서 Lazy 를 허용하지 않고, 즉시 값을 읽어 들인다. Lazy를 설정할 수 있지만, 동작하지 않는다.

@OneToOne에서 Lazy Loading을 가능하게 하기 위해서는 다음과 같은 처리가 필요하다.

  • nullable이 허용되지 않는 @OneToOne 관계. (ex: Plan과 PlanResult)
  • 양방향이 아닌, 단방향 @OneToOne 관계. (parent -> child)
  • @PrimaryKeyJoin은 허용되지 않음.

위 3가지 조건을 모두 만족하는 code는 다음과 같다.

    @OneToOne(fetch = FetchType.LAZY, optional = false, cascade = CascadeType.ALL)
    @JoinColumn(name = "bookId", nullable = false, unique = true)

양방향 @OneToOne Lazy loading entity

기본적으로 Lazy Loading을 위해서는 양방향 으로는 되지 않는다. 되게 하기 위해서는 다음 site들을 참고하길 바란다.

Summary

  • Hibernate에서 @OneToOne은 피할수 있으면 최대한 피하라. (@SecondTable과 같은 방법이 있다.)
  • @OneToMany와 @OneToOne은 parent, child의 저장 순서가 다르다.
    • @OneToOne에서는 child가 먼저 저장이 되어야지 되고, @OneToMany는 parent가 먼저 저장이 되어야지 된다.
  • @OneToOne을 Lazy loading하고자 하면, 반드시 다음 3가지 조건을 지켜야지 된다.
    • nullable이 허용되지 않는 @OneToOne 관계만이 허용된다. (ex: Plan과 PlanResult)
    • 양방향이 아닌, 단방향 @OneToOne 관계만이 허용된다. (parent -> child)
    • @PrimaryKeyJoin은 허용되지 않는다.


Posted by Y2K
,