본문 바로가기

Architecture for Software/Java

[Hibernate Annotation] 엔티티 빈의 연관 관계 및 관계 매핑: 1대 1 관계

최근 Hibernate 3.x를 이용하여 개발을 하고 있습니다. 예전에 Hibernate 2.x를 사용하였을때과 가장 큰 차이점이 Hibernate Annotations(http://www.hibernate.org/hib_docs/annotations/reference/en/html_single/)이 도입되었다는 점입니다.

아~ 정말 편리해졌습니다. hbm.xml을 손수 고칠때와는 완전히 다르더군요 ;-)

Hibernate 3.x에서 Annotation이 대폭 강화되면서 이제 정말 Hibernate를 편리하게 사용해볼만 할것 같다는 생각이 듭니다. 사실 Hibernate를 많이 다루지 않았기 때문에 깊숙히 알고 있지는 않습니다. 이번 기회에 Hibernate를 깊이 익혀볼 생각입니다.

우선 Hibernate Annotation의 기능중에 1대1관계(One-to-One)를 설정하는 부분에 대하여 살펴보고자 합니다.
원문은 http://www.hibernate.org/hib_docs/annotations/reference/en/html_single/#entity-mapping-association 입니다.

 



2.2.5. 엔티티빈의 연관관계 및 관계 매핑(Mapping entity bean associations/relationships)

2.2.5.1 1대1(One-to-one) 관계

@OneToOne 어노테이션을 사용하여 1대1 관계를 엔티티 빈에 연관관계를 설정할 수 있다. 1대1 관계를 설정하는 경우에는 3가지이다.

  1. 연관된 엔티티 빈이 같은 PK(Primary Key) 값을 공유하고 있을 때거나
  2. 다른 엔티티 빈에 FK(Foreign Key)를 가지고 있는 경우나 (주의: 이 FK 컬럼은 데이터베이스 안에서 반드시 유일성 제약조건constrained unique으로 설정되어 1대1 다중 관계를 가지는 경우이다.)
  3. 또는 연관 테이블에서 3개의 엔티티 사이의 연결을 위하여 링크를 저장하여 사용할 경우이다. (유일성 제약조건은 각 FK가 1대1 다중 관계를 보장하기 위하여 정의된다.)

첫번째로, 공유된 PK를 사용하여 실제로 1대1 연관관계를 지정해보자.

@Entity
public class Body {
    @Id
    public Long getId() { return id; }

    @OneToOne(cascade = CascadeType.ALL)
    @PrimaryKeyJoinColumn
    public Heart getHeart() {
        return heart;
    }
    ...
}

@Entity
public class Heart {
    @Id
    public Long getId() { ...}
}
1대1일 관계는 실제로 @PrimaryKeyJoinColumn 어노테이션을 사용하여 표기된다.

다음 예제는 FK 컬럼을 통하여 연관된 엔티티들이 관계를 맺는 경우이다.

@Entity
public class Customer implements Serializable {
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name="passport_fk")
    public Passport getPassport() {
        ...
    }

@Entity
public class Passport implements Serializable {
    @OneToOne(mappedBy = "passport")
    public Customer getOwner() {
    ...
}

Customer 엔티티 빈은 Passport 엔티티 빈에 연결되어 있으며, Customer 테이블안에 passport_fk 라는 컬럼이 FK로 설정되어 있다. 

연결되는 컬럼은 @JoinColumn 어노테이션으로 정의되는데, @Column 어노테이션과 유사하다. 

@JoinColumn 어노테이션은 referencedColumnName 이라는 하나 이상의 인자(Parameter)를 가진다.이 인자는 연결관계를 맺을때 목적이 되는 엔티티의 컬럼을 지정하는데 사용한다.

referencedColumnName 를 PK가 아닌 컬럼에 사용할때는 연관되는 클래스는 Serializable을 구현하여야 한다는 점에 주의하자.

또한 referencedColumnName을 PK가 아닌 컬럼에 사용할때는 하나의 컬럼을 프로퍼티에 지정할 수 있다는 점에 주의하자.(다른 경우에는 아마 동작하지 않을 것이다.)

연관관계는 양방향성을 가질 것이다. 양방향성 관계는 어느 한쪽에서(또는 다른 쪽에서) 연관관계에 대한 소유권을 가지는 것이다. 소유권을 가진 소유가자되면 연관관계가 있는 컬럼(join column)을 업데이트를 위한 책임을 가진다.

이러한 관계에서 한쪽의 책임을 회피시키려면 mappedBy 속성을 사용하면된다. mappedBy는 소유권을 가진 쪽에서 연관관계를 맺는 프로퍼티 명을 참조한다. 예제에서는 passport가 이런 경우이다.

여러분이 확인한바와같이 소유권을 가진 다른 한쪽에서 이미 정의하였다면 연관관계를 가지는 컬럼(join column)을 정의할 필요가 (반드시) 없다.

만약 @JoinColumn 을 사용하지 않고 소유권을 가진 다른 한쪽을 정의한다면, 기본값이 적용된다. join column은 소유자의 테이블에서 생성될 것이며, 이름은 소유자 측의 관계의 이름의 연속이 될 것이다.

즉 “_”를 사용하여 “관계명_관계명” 과 같은 컬럼명을 가지게 될 것이며, 소유자 측의 PK 컬럼의 이름이다.

이 예제의 경우 passport_id는 프로퍼티명이 passport이고 Passport 엔티티빈의 컬럼 ID가 id라는 것을 나타낸다.

세번째 가능한 경우는 연관 테이블(매핑 테이블)을 이용하는 매우 낮선 경우이다.

@Entity
public class Customer implements Serializable {
    @OneToOne(cascade = CascadeType.ALL)
    @JoinTable(name = "CustomerPassports",
        joinColumns = @JoinColumn(name="customer_fk"),
        inverseJoinColumns = @JoinColumn(name="passport_fk")
    )
    public Passport getPassport() {
        ...
    }

@Entity
public class Passport implements Serializable {
    @OneToOne(mappedBy = "passport")
    public Customer getOwner() {
    ...
}

CustomerPassportCustomerPassports라는 이름의 연관 테이블을 통하여 연결되어 있다. 이 연관테이블은 passport_fk라는 FK를 Passport테이블을 지정하기 위하여 가지고 있다. (inverseJoinColumn에 의하여 구체화되며, Customer 을 지정하기 위하여 customer_fk라는 이름의 FK를 joinColumns 속성에 의하여 구체화하였다.)

여러분은 반드시 이러한 매핑시  연관관계가 있는 테이블명과 컬럼(join column)을 명시적으로 정의하여야 한다.




다음에 계속 이어나가겠습니다.

한가지 궁금한게 아직 국내에서 Hibernate Annotation 문서를 번역한게 없나요? 아직 제가 못찾았는데... 번역된것이 있을듯도 한데.. 중요한 부분만 번역하려고 하는데.. 문서가 있다면 굳이 할 필요는 없을듯 합니다.

만약 Hibernate Annotation에 대한 국내문서를 알고 계신 분이 있으시다면 알려주시기 바랍니다.

감사합니다. :-)