DB의 설계상에서 key를 단일 키만 사용하는 경우는 거의 없습니다.
대부분 복합 key를 사용하여 DB 설계를 하고 있습니다.
그렇다면 JPA에서 복합 키를 어떻게 표현할 수 있을까요?
그 방법에 대해 차근차근 살펴보겠습니다.
식별, 비식별 관계
DB에서는 FK(외래 키)가 기본 키에 포함되는지 여부에 따라 식별, 비식별 관계로 구분됩니다.
보통 비식별 관계를 주로 사용하고 필요한 상항에서 식별 관계를 사용합니다.
- 식별 관계 : 부모 테이블의 기본 키를 받아 자식 테이블의 기본 키 + 외래 키로 사용
- 비식별 관계 : 부모 테이블의 기본 키를 받아서 자식 테이블의 외래 키 로 사용
- 필수적 비식별 관계 : 외래 키에 NULL을 허용하지 않음
- 선택적 비식별 관계 : 외래 키에 NULL을 허용
복합 키를 표현할 때는 JPA에서는 식별자 클래스를 만들어야 합니다.
그리고 식별자 클래스는 @IdClass, @EmbededId 두 개의 어노테이션 중 하나를 선택해서 사용하면 됩니다.
간단하게 말하자면 @IdClass는 DB에 가까운 방법, @EmbededId는 객체지향에 가까운 방법입니다.
아래에서 설명할 식별자 클래스의 equals(), hashCode()는 영속성 컨텍스트가 식별자를 비교할때 사용하기 때문에 반드시 오버라이딩 해야합니다.
비식별 관계 매핑
@IdClass 사용
@Entity
@IdClass(ParentId.class)
public class Parent {
@Id
@Column(name = "parent_id1")
private String id1;
@Id
@Column(name = "parent_id2")
private String id2;
private String name;
}
public class ParentId implements Serializable {
private String id1;
private String id2;
public ParentId() {
}
public ParentId(String id1, String id2) {
this.id1 = id1;
this.id2 = id2;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ParentId parentId = (ParentId) o;
return id1.equals(parentId.id1) &&
id2.equals(parentId.id2);
}
@Override
public int hashCode() {
return Objects.hash(id1, id2);
}
}
@Entity
public class Child {
@Id
private String id;
@ManyToOne
@JoinColumns({
@JoinColumn(name = "parent_id1", referencedColumnName = "parent_id1"),
@JoinColumn(name = "parent_id2", referencedColumnName = "parent_id2")
})
private Parent parent;
}
코드를 순서대로 살펴보겠습니다.
복합 키를 사용하기 위해선 식별자 클래스를 만들어야 합니다. 해서 ParentId 식별자 클래스를 생성하였습니다.
Parent에서 @IdClass(ParentId.class)를 선언하여 식별자 클래스를 지정하였습니다.
식별자 클래스에는 몇 가지 특징이 있습니다.
- 식별자 클래스의 필드명과 엔티티에서 사용하는 식별자의 필드명이 동일해야 합니다.
Parent.id1, ParentId.id1이 서로 동일한 필드명을 사용하고 있습니다. - Serializable 인터페이스를 구현해야 합니다.
- equals(), hashCode()를 구현해야 한다.
- 기본 생성자를 선언해야 합니다.
- 클래스의 접근 제한자는 public이어야 합니다.
Child에서는 복합 키 매핑을 위해 @JoinColumns를 이용하고 각각의 key는 @JoinColumn으로 매핑하였습니다.
referencedColumnName 속성이 name 속성과 동일할 때 생략 가능하나, 미 지정했을 때 @JoinColumns에 선언된 @JoinColumn의 순서가 바뀌어 저장 될수 있습니다. 꼭 referencedColumnName 속성을 지정하길 권장 합니다. (링크)
@EmbeddedId 사용
@Entity
public class Parent {
@EmbeddedId
private ParentId id1;
private String name;
}
@Embeddable
public class ParentId implements Serializable {
@Column(name = "parent_id1")
private String id1;
@Column(name = "parent_id2")
private String id2;
public ParentId() {
}
public ParentId(String id1, String id2) {
this.id1 = id1;
this.id2 = id2;
}
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
}
Parent에서 식별자 클래스 타입의 필드를 생성하고 @EmbeddedId을 사용하여 비식별 관계 매핑을 해주었습니다.
@IdClass와는 다르게 @EmbeddedId를 적용한 식별자 클래스는 식별자 클래스에 기본 키를 직접 매핑 합니다.
@EmbeddedId를 사용할때는 아래의 조건이 있습니다.
- 식별자 클래스에 @Embeddable을 사용해야 한다.
- Serializable 인터페이스를 구현해야 합니다.
- equals(), hashCode()를 구현해야 한다.
- 기본 생성자를 선언해야 합니다.
- 클래스의 접근 제한자는 public이어야 합니다.
식별 관계 매핑
@IdClass 사용
@Entity
public class Parent {
@Id
@Column(name = "parent_id")
private String id1;
private String name;
}
@Entity
@IdClass(ChildId.class)
public class Child {
@Id
@ManyToOne
@JoinColumn(name = "parent_id")
private Parent parent;
@Id
@Column(name = "child_id")
private String childId;
private String name;
}
public class ChildId implements Serializable {
private String parent; //Child.parent 매핑
private String childId; //Child.childId 매핑
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
}
@Entity
@IdClass(GrandChildId.class)
public class GrandChild {
@Id
@ManyToOne
@JoinColumns({
@JoinColumn(name = "parent_id", referencedColumnName = "parent_id"),
@JoinColumn(name = "child_id", referencedColumnName = "child_id")
})
private Child child;
@Id
@Column(name = "grandchild_id")
private String id;
private String name;
}
public class GrandChildId implements Serializable {
private ChildId child; //GrandChild.child 매핑
private String id; //GrandChild.id 매핑
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
}
식별 관계는 기본 키와 외래 키를 같이 매핑 해야 합니다. @Id와 연관관계 매핑인 @ManyToOne을 같이 사용하여 매핑 하였습니다.
@EmbeddedId 사용
@Entity
public class Parent {
@Id
@Column(name = "parent_id")
private String id1;
private String name;
}
@Entity
public class Child {
@EmbeddedId
private ChildId id;
@MapsId("parentId") //ChildId.parentId 매핑
@ManyToOne
@JoinColumn(name = "parent_id")
private Parent parent;
private String name;
}
@Embeddable
public class ChildId implements Serializable {
private String parentId; //@MapsId("parentId") 매핑
@Column(name = "child_id")
private String id;
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
}
@Entity
public class GrandChild {
@EmbeddedId
private GrandChildId id;
@MapsId("childId") //GrandChildId.childId 매핑
@ManyToOne
@JoinColumns({
@JoinColumn(name = "parent_id", referencedColumnName = "parent_id"),
@JoinColumn(name = "child_id", referencedColumnName = "child_id")
})
private Child child;
private String name;
}
@Embeddable
public class GrandChildId implements Serializable {
private ChildId childId; //@MapsId("childId") 매핑
@Column(name = "grandchild_id")
private String id;
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
}
@EmbeddedId는 식별 관계로 매핑할때 @MapsId를 사용하면 됩니다.
@MapsId는 외래 키와 매핑한 연관관계를 기본 키에도 매핑하겠다는 뜻입니다. 속성값은 @EmbeddedId를 사용한 필드명을 지정합니다.
참고
자바 ORM 표준 JPA 프로그래밍, 김영한, 에이콘
'ORM(JPA)' 카테고리의 다른 글
[JPA] 상속 관계 매핑 (0) | 2020.02.03 |
---|---|
[JPA] 연관관계 매핑 - 1:1(일대일), N:N(다대다) (0) | 2020.02.01 |
[JPA] 연관관계 매핑 - N:1(다대일), 1:N(일대다) (0) | 2020.01.31 |
댓글