사용자 도구

사이트 도구


java:jpa:one-to-one

JPA One-To-One

Primary Key 기반 One-To-One JPA 2 방식

  • @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이 잘 작동하지 않는다.
    1. 이유는 null 값이 가능한 OneToOne의 경우(optional=true 상황) 프록시 객체로 감쌀 수 없기 때문이다.
    2. 만약 null 값이 가능한 OneToOne 에 프록시 객체를 넣는다면, 이미 그 순간 결코 null 이 아닌 프록시 객체를 리턴하는 상태가 돼 버리기 때문이다.
    3. 따라서 JPA 구현체는 기본적으로 One-To-One 관계에 Lazy 를 허용하지 않고, 즉시 값을 읽어 들인다.
  • 참조
  • 결코 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 구현

  • 기본적으로 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<String> contentHolder;
 
public void setContent(String content) {
	if (getContentHolder() == null) {
		setContentHolder(new ArrayList<String>());
	}
 
	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 구현

  • 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로 지정해도 부모값을 읽는 쿼리가 날라간다.
  • 위와 같이 만들면 꼭 필요할 경우에만 자식의 값을 읽을 수 있다. 단, 매핑은 부모쪽에서 관리하며 부모쪽 테이블에 자식을 가리키는 외래키 컬럼이 추가된다.
  • 값을 저장할 때는 부모 객체에 자식 객체를 설정하여 저장하면 자식까지 저장된다.
java/jpa/one-to-one.txt · 마지막으로 수정됨: 2017/02/21 16:38 저자 kwon37xi