7장_ 고급 매핑-2
Updated:
저번 포스트에 이어서 고급 매핑에 대해 더 알아보자
3. 복합 키와 식별 관계 매핑
3-3. 복합 키: 식별 관계 매핑
복합 키와 식별 관계를 알아보자
부모, 자식, 손자까지 계속 키본 키를 전달한다.
이는 부모 테이블의 기본 키를 포함해서 복합 키를 구성해야 하므로 @IdClass나 @EmbeddedId를 사용한다.
먼저 @IdClass로 식별 관계를 매핑해보자
// 부모
@Entity
public class Parent {
@Id @Column(name = "PARENT_ID")
private String id;
private String name;
...
}
// 자식
@Entity
@IdClass(ChildId.class)
public class Child {
@Id
@ManyToOne
@JoinColumn(name = "PARENT_ID")
public Parent parent;
@Id @Column(name = "CHILD_ID")
private String childId;
private String name;
...
}
// 자식 ID
public class ChildId implements Serializable {
private String parent; // Child.parent 매핑
private String childId; // Child.childId 매핑
// equals, hashCode
...
}
// 손자
@Entity
@IdClass(GrandChildId.class)
public class GrandChild {
@Id
@ManyToOne
@JoinColumns({
@JoinColumn(name = "PARENT_ID"),
@JoinColumn(name = "CHILD_ID")
})
public Child child;
@Id @Column(name = "GRANDCHILD_ID")
private String id;
private String name;
...
}
// 손자 ID
public class GrandChildId implements Serializable {
private ChildId childId; // GrandChild.child 매핑
private String id; // GrandChild.id 매핑
// equals, hashCode
...
}
다음으로 @EmbeddedId로 식별 관계를 구성해보자
@IdClass와 다른 점은 @Id 대신에 @MapsId를 사용한 점이다.
@MapsId는 외래 키와 매핑한 연관관계를 기본 키에도 매핑하겠다는 뜻이다.
// 부모
@Entity
public class Parent {
@Id @Column(name = "PARENT_ID")
private String id;
private String name;
...
}
// 자식
@Entity
public class Child {
@EmbeddedId
private ChildId id;
@MapsId("parentId") // ChildId.parentId 매핑
@ManyToOne
@JoinColumn(name = "PARENT_ID")
public Parent parent;
private String name;
...
}
// 자식 ID
@Embeddable
public class ChildId implements Serializable {
private String parentId; // @MapsId("parentId")로 매핑
@Column(name = "CHILD_ID")
private String id;
// equals, hashCode
...
}
// 손자
@Entity
public class GrandChild {
@EmbeddedId
private GrandChildId id;
@MapsId("childId") // GrandChildId.childId 매핑
@ManyToOne
@JoinColumns({
@JoinColumn(name = "PARENT_ID"),
@JoinColumn(name = "CHILD_ID")
})
public Child child;
private String name;
...
}
// 손자 ID
@Embeddable
public class GrandChildId implements Serializable {
private ChildId childId; // @MapsId("childId")로 매핑
@Column(name = "GRANDCHILD_ID")
private String id;
// equals, hashCode
...
}
3-4. 비식별 관계로 구현
방금 예를 들었던 식별 관계를 비식별 관계로 변경해보자
식별 관계의 복합 키를 사용한 코드와 비교하면 매핑도 쉽고 코드도 단순하다.
그리고 복합 키가 없으므로 복합 키 클래스를 만들지 않아도 된다.
// 부모
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
...
}
// 자식
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "PARENT_ID")
private Parent parent;
...
}
// 손자
@Entity
public class GrandChild {
@Id @GeneratedValue
@Column(name = "GRANDCHILD_ID")
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "CHILD_ID")
private Child child;
...
}
3-5. 일대일 식별 관계
일대일 식별 관계는 조금 특별하다.
일대일 식별 관계는 자식 테이블의 기본 키 값으로 부모 테이블의 기본 키 값만 사용한다.
그래서 부모 테이블의 기본 키가 복합 키가 아니라면 자식 테이블의 기본 키는 복합 키로 구성하지 않아도 된다.
// 부모
@Entity
public class Board {
@Id @GeneratedValue
@Column(name = "BOARD_ID")
private Long id;
private String title;
@OneToOne(mappedBy = "board")
private BoardDetail boardDetail;
...
}
// 자식
@Entity
public class BoardDetail {
@Id
private Long boardId;
/**
* BoardDetail.boardId 매핑
* BoardDetail처럼 식별자가 단순히 컬럼 하나면 @MapsId를 사용하고 속성 값은 비워두면 된다.
*/
@MapsId
@OneToOne
@JoinColumn(name = "BOARD_ID")
private Board board;
private String content;
...
}
4. 조인 테이블
데이터베이스 테이블의 연관관계를 설계하는 방법은 크게 2가지다.
- 조인 컬럼 사용 (외래키)
- 조인 테이블 사용 (테이블 사용)
객체와 테이블을 매핑할 때 조인 컬럼은 @JoinColumn으로 매핑하고 조인 테이블은 @JoinTable로 매핑한다.
조인 테이블은 주로 다대다 관계를 일대다, 다대일 관계로 풀어내기 위해 사용한다.
그렇지만 일대일, 일대다, 다대일 관계에서도 사용한다.
참고로 조인 테이블은 연결 테이블, 링크 테이블로도 부른다.
지금부터 일대일, 일대다, 다대일, 다대다 관계를 조인 테이블로 매핑해보자
4-1. 일대일 조인 테이블
// 부모
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@OneToOne
@JoinTable(name = "PARENT_CHILD",
joinColumns = @JoinColumn(name = "PARENT_ID"),
inverseJoinColumns = @JoinColumn(name = "CHILD_ID"))
private Child child;
...
}
// 자식
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
private String name;
...
}
부모 엔티티를 보면 @JoinColumn 대신 @JoinTable을 사용했다.
@JoinTable의 속성을 다시 한번 살펴보자
- name: 매핑할 조인 테이블 이름
- joinColumns: 현재 엔티티를 참조하는 외래 키
- inverseJoinColumns: 반대방향 엔티티를 참조하는 외래 키
양방향으로 매핑하려면 다음 코드를 추가하면 된다.
public class Child {
...
@OneToOne(mappedBy = "child")
private Parent parent;
}
4-2. 일대다 조인 테이블
// 부모
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@OneToMany
@JoinTable(name = "PARENT_CHILD",
joinColumns = @JoinColumn(name = "PARENT_ID"),
inverseJoinColumns = @JoinColumn(name = "CHILD_ID"))
private List<Child> child = new ArrayList<Child>();
...
}
// 자식
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
private String name;
...
}
4-3. 다대일 조인 테이블
다대일은 일대다에서 방향만 반대이므로 아래와 같이 매핑한다.
// 부모
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "parent")
private List<Child> child = new ArrayList<Child>();
...
}
// 자식
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
private String name;
@ManyToOne(optional = false)
@JoinTable(name = "PARENT_CHILD",
joinColumns = @JoinColumn(name = "CHILD_ID"),
inverseJoinColumns = @JoinColumn(name = "PARENT_ID"))
private Parent parent;
...
}
4-4. 다대다 조인 테이블
다대다 관계를 만들려면 조인 테이블의 두 컬럼을 합해서 하나의 복합 유니크 제약조건을 걸어야 한다.
참고로 조인 테이블에 컬럼을 추가하면 @JoinTable 전략을 사용할 수 없다.
대신에 새로운 엔티티를 만들어서 조인 테이블과 매핑해야 한다.
// 부모
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@ManyToMany
@JoinTable(name = "PARENT_CHILD",
joinColumns = @JoinColumn(name = "PARENT_ID"),
inverseJoinColumns = @JoinColumn(name = "CHILD_ID"))
private List<Child> child = new ArrayList<Child>();
...
}
// 자식
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
private String name;
...
}
5. 엔티티 하나에 여러 테이블 매핑
잘 사용하지는 않지만 @SecondaryTable을 사용하면 한 엔티티에 여러 테이블을 매핑할 수 있다.
@Entity
@Table(name = "BOARD")
@SecondaryTable(name = "BOARD_DETAIL"
, pkJoinColumns = @PrimaryKeyJoinColumn(name = "BOARD_DETAIL_ID"))
public class Board {
@Id @GeneratedValue
@Column(name = "BOARD_ID")
private Long id;
private String title;
@Column(table = "BOARD_DETAIL")
private String content;
...
}
Board 엔티티는 @Table을 사용해서 BOARD 테이블과 매핑했다.
그리고 @SecondaryTable을 사용해서 BOARD_DETAIL 테이블을 추가로 매핑했다.
@SecondaryTable 속성은 다음과 같다.
- @SecondaryTable.name: 매핑할 다른 테이블의 이름, 예제에서는 테이블명을 BOARD_DETAIL로 지정했다
- @SecondaryTable.pkJoinColumns: 매핑할 다른 테이블의 기본 키 컬럼 속성
content 필드는 BOARD_DETAIL 테이블의 컬럼에 매핑했다. title 필드처럼 테이블을 지정하지 않으면 기본 테이블인 BOARD에 매핑된다.
더 많은 테이블을 매핑하려면 @SecondaryTables를 사용하면 된다.
@SecondaryTables({
@SecondaryTable(name = "BOARD_DETAIL"),
@SecondaryTable(name = "BOARD_FILE")
})
참고로 @SecondaryTables를 사용해서 두 테이블을 하나의 엔티티에 매핑하는 방법보다는 테이블당 엔티티를 각각 만들어서 일대일 매핑하는 것을 권장한다.
이 방법은 항상 두 테이블을 조회하므로 최적화하기 어렵다.
Leave a comment