====== JPA One-To-One ====== * [[http://www.mkyong.com/hibernate/hibernate-one-to-one-relationship-example-annotation/|Hibernate One-To-One Primary Key join Relationship Example with JPA Annotation]] * One-To-One 관계가 정말로 필요한지 고민하라. * [[http://docs.oracle.com/javaee/6/api/javax/persistence/SecondaryTable.html|@SecondaryTable]]이나 [[http://docs.oracle.com/javaee/6/api/javax/persistence/Embeddable.html|@Embeddable]] 등으로 해결 가능할 수도 있다. ===== Primary Key 기반 One-To-One JPA 2 방식 ===== * **[[http://docs.oracle.com/javaee/6/api/javax/persistence/MapsId.html|@MapsId]]**를 사용하라. * ''MapsId''는 기본적으로 Composite Primary Key(Embeddable)의 일부만 관계에서 사용될때 ''value''에서 해당 property를 명시한다. 명시하지 않으면 전체 사용. * Hibernate에서 ''@MapsId''로 OneToOne 매핑시에 자식측에 ''@JoinColumn''이 필요하다. @Entity class MedicalHistory implements Serializable { @Id @Column(name = "patient_id") Integer id; @MapsId @OneToOne @JoinColumn(name = "patient_id") Person patient; } @Entity class Person { @Id @GeneratedValue Integer id; } * 주의! 양뱡향 매핑의 경우, **부모에 자식값을 설정하고, 자식에도 부모 값을 설정**해야 부모를 저장하면 정상적으로 자식도 저장된다. ===== Primary Key 기반 One-To-One JPA 1 방식===== // 부모측 @Id @Column(name="id") @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @OneToOne(cascade = CascadeType.ALL, mappedBy="parent") @PrimaryKeyJoinColumn private Child child; // 자식측 @Id @GenericGenerator(name = "foreign_one_to_one_generator", strategy = "foreign", parameters = @Parameter(name = "property", value = "parent")) @GeneratedValue(generator = "foreign_one_to_one_generator") @Column(name = "parent_id") private Long id; @OneToOne(fetch = FetchType.LAZY) @PrimaryKeyJoinColumn(name = "parent_id", referencedColumnName = "id") private Parent parent; * 주의! 양뱡향 매핑의 경우, **부모에 자식값을 설정하고, 자식에도 부모 값을 설정**해야 부모를 저장하면 정상적으로 자식도 저장된다. ===== One-To-One 과 Lazy Loading ===== * One-To-One 에서는 Lazy Loading이 잘 작동하지 않는다. - **이유는 null 값이 가능한 OneToOne의 경우(''optional=true'' 상황)** 프록시 객체로 감쌀 수 없기 때문이다. - 만약 null 값이 가능한 OneToOne 에 프록시 객체를 넣는다면, 이미 그 순간 결코 null 이 아닌 프록시 객체를 리턴하는 상태가 돼 버리기 때문이다. - 따라서 JPA 구현체는 기본적으로 One-To-One 관계에 Lazy 를 허용하지 않고, 즉시 값을 읽어 들인다. * [[java:hibernate:lazy_to_one|@LazyToOne]] 참조 * 참조 * [[http://community.jboss.org/wiki/SomeExplanationsOnLazyLoadingone-to-one|Some explanations on lazy loading (one-to-one) | Hibernate | JBoss Community]] * [[http://justonjava.blogspot.com/2010/09/lazy-one-to-one-and-one-to-many.html|Just on JAVA: Lazy one-to-one inverse relationships in Hibernate]] * [[http://netframework.tistory.com/430|Programming is Fun :: Hibernate에서 Bi-Direction @OneToOne 이용]] - 이 위키페이지 내용과 여기에 참조 링크등을 잘 정리한 글. * [[http://whiteship.me/?p=13301|하이버네이트 OneToOne 연관 관계 Lazy Fetching이 안 먹어!?]] : OneToOne 단방향 관계이고 부모측 테이블에 자식의 주키가 저장될 경우에는 null 여부와 무관하게 Lazy Loading이 된다는 사실에 대한 설명. 부모측 테이블에 값이 저장돼 있기 때문에 다른 테이블을 읽지 않아도 null 혹은 프록시 객체 주입을 결정할 수 있기 때문. * **결코 null 일 수 없는 One-To-One 관계(''optional=false'')**에서는 프록시를 설정하고 Lazy 로 작동하게 만드는 것이 가능하다. **optional=false** 를 지정한다(결코 Null일 수 없다는 뜻). * **아래 코드는 잘못되었다. PrimaryKeyJoin 의 경우에는 optional=false 일 경우에 데이터 저장 순서가 꼬여버린다. 정상적인 optional=false가 작동하려면 ForeignKey Join을 해야한다.** * @OneToOne(cascade = CascadeType.ALL, mappedBy = "parent", optional = false, fetch=FetchType.LAZY) @PrimaryKeyJoinColumn private Child child; * ''optional=false'' 는 Hibernate XML 매핑시 ''constrained=true'' 와 같다. ===== 게시판 형태에서 게시글에 대해 @ElementCollection을 사용한 LazyLoading 구현 ===== * Deprecated. see [[java:hibernate:lazy_to_one|@LazyToOne]] * 기본적으로 One-To-One의 LazyLoading이 난잡하므로 @ElementCollection에 Lazy를 이용하되, **컬렉션에 Unique 조건을 주므로써 딱 1개의 데이터만 들어갈 수 있도**록 처리한다. // Article 이라는 엔티티의 내용(content)를 LazyLoading하는 예제이다. @ElementCollection(fetch = FetchType.LAZY) @CollectionTable(name = "article_content_holder", joinColumns = @JoinColumn(name = "article_id", unique = true)) @org.hibernate.annotations.ForeignKey(name="fk_article_content_holder_id") @Column(name = "content", length = 1024) private List contentHolder; public void setContent(String content) { if (getContentHolder() == null) { setContentHolder(new ArrayList()); } getContentHolder().clear(); getContentHolder().add(content); } public String getContent() { if (getContentHolder() == null || getContentHolder().size() == 0) { return null; } return getContentHolder().get(0); } * 실제 클라이언트는 getCentent(), setContent() 메소드만 사용한다. * unique=true 조건 때문에 컬렉션임에도 단 한개의 값만 저장할 수 있다. * 복합키 엔티티일 경우 ''@CollectionTable(uniqueConstraints = @UniqueConstraint(columnNames={"some_id", "some_version"}))'' 형태로 복합키에 unique 조건을 줄 수 있다. * 값을 수정할 경우, update 문이 아니라 delete/insert 문이 article_content_holder 테이블측에 수행된다. ===== 게시판 형태에서 게시글에 대해 One-To-One LazyLoading 구현 ===== * Deprecated. see [[java:hibernate:lazy_to_one|@LazyToOne]] * ElementCollection 방식 추천. * 게시판처럼 내용이 있지만, 목록에서는 내용을 보여주지 않는 경우 JPA 에서 내용 컬럼에 LazyLoading을 적용해도 현재의 JPA 구현체들이 필드 LazyLoading을 구현하지 못해서 결국 제목의 목록만 필요할 때도 내용까지 읽어들인다. * 이 경우 해결책은 내용부분을 다른 테이블로 분리하고 One-To-One 관계로 맺은 뒤에 내용 부분에 LazyLoading을 적용하는 것이다. * 단, "PrimaryKeyJoin" 방식은 LazyLoading이 사실상 불가능했다. * optional=false를 붙일 경우 부모 부분을 먼저 저장하지 않고 자식(내용부분)을 먼저 저장하려 들기 때문에 부모의 PrimaryKey 가 없어서 오류가 발생한다. * optional=true를 할 경우 저장은 잘 되지만 Lazy Loading은 작동하지 않게 된다. * 따라서 **Foreign Key One-To-One** 관계를 사용해야 한다. * // Parent.java 에서 @OneToOne(cascade = CascadeType.ALL, optional = false, fetch = FetchType.LAZY) @JoinColumn(name="child_id", nullable = false, unique = true, insertable = true, updatable = false) @org.hibernate.annotations.ForeignKey(name="fk_parent_child_id" private Child child; // Child.java 에서 @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Lob @Column(name="body", nullable=false) private String body; // 자식측에 Parent 로 가는 매핑을 넣지 말것. 그 경우 Lazy로 지정해도 부모값을 읽는 쿼리가 날라간다. * 위와 같이 만들면 꼭 필요할 경우에만 자식의 값을 읽을 수 있다. 단, 매핑은 부모쪽에서 관리하며 **부모쪽 테이블에 자식을 가리키는 외래키 컬럼이 추가된다**. * 값을 저장할 때는 부모 객체에 자식 객체를 설정하여 저장하면 자식까지 저장된다.