📌 상속 관계 매핑
관계형 데이터베이스에는 상속이 없다. 그나마 '슈퍼타입-서브타입 관계' 모델링 기법이 상속과 비슷하다.
즉, ORM에서의 상속 관계 매핑은 이 모델링 기법이고 3가지 방법으로 구현할 수 있다.
- 조인 전략 : 각각을 모두 테이블로 만들고 조회 시 조인을 사용한다.
- 단일 테이블 전략 : 테이블을 하나만 사용해 통합한다.
- 구현 클래스 테이블 전략 : 서브 타입마다 하나의 테이블을 만든다.
1️⃣ 조인 전략
모두 테이블로 만들고 자식 테이블이 부모 테이블의 기본 키를 받아 기본 키 + 외래 키로 사용한다.
테이블은 타입의 개념이 없어 타입을 구분하는 칼럼을 추가해야 한다.
@Entity
@Inheritance(strategy = InheritanceType.JOINED) // 상속의 부모 클래스, 전략 : 조인
@DiscriminatorColumn(name = "DTYPE") // 구분 칼럼 지정
public abstract class Item {
@Id @GeneratedValue
@Column(name="item_id")
private Long id;
...
}
@Entity
@DiscriminatorValue("A") // 자식 클래스, 구분 칼럼에 "A" 지정
public class Album extends Item {
...
}
😋 매핑 정보 분석하기
- @Inheritance(stratege = " ") : 부모 클래스에 사용한다. 매핑 전략을 지정해야 한다.
- @DiscriminatorColumn(name = " ") : 부모 클래스에 붙여 자식 테이블을 구분하게 해준다.
- @DiscriminatorValue(" ") : 엔티티를 저장할 때 구분 칼럼에 입력할 값 지정한다.
- @PrimaryKeyJoinColumn(name = " ") : 자식 테이블의 기본 키 칼럼명을 변경할 때 쓴다.
🧷 장점
- 테이블이 정규화
- 외래 키 참조 무결성 제약조건을 활용할 수 있다.
- 저장공간을 효율적으로 사용한다.
🧷 단점
- 조회할 때 조인을 많이 사용해 성능이 저하될 수 있다.
- 조회 쿼리가 복잡하다.
- 데이터를 등록할 INSERT SQL을 두 번 실행한다.
🧷 특징
- JPA 표준 명세는 구분 칼럼을 사용하도록 하지만 Hibernate를 포함한 몇몇 구현체는 없이도 동작한다.
2️⃣ 단일 테이블 전략
이름 그대로 테이블을 단 하나만 사용한다.
구분 컬럼으로 어떤 자식 데이터가 저장되었는지 꼭 구분해야 한다.
만약 Book을 저장하면 Actor 같은 다른 칼럼을 사용하지 않기 때문에 매핑한 칼럼 모두 null을 허용해야 한다.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) // 상속의 부모 클래스, 전략 : 단일 테이블
@DiscriminatorColumn(name = "DTYPE") // 구분 칼럼 지정
public abstract class Item {
@Id @GeneratedValue
@Column(name="item_id")
private Long id;
...
}
@Entity
@DiscriminatorValue("A")
public class Album extends Item { ... }
@Entity
@DiscriminatorValue("B")
public class Movie extends Item { ... }
🧷 장점
- 조인이 필요 없어서 조회가 빠르고 쿼리가 단순하다.
🧷 단점
- 자식 엔티티가 매핑한 칼럼 모두 null을 허용해야 한다.
- 단일 테이블에 모든 것을 저장해서 테이블이 커질 수 있다. 즉, 상황에 따라 성능이 떨어진다.
🧷 특징
- 구분 칼럼을 꼭 사용해야 한다. (@DiscriminatorColumn)
- 구분 칼럼을 지정하지 않으면 엔티티 이름을 사용한다.
3️⃣ 구현 클래스마다 테이블 전략
자식 엔티티마다 테이블을 만들고, 각 엔티티에 필요한 칼럼을 모두 넣는다.
일반적으로 추천하지 않는 전략이다.
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) // 상속의 부모 클래스, 전략 : 구현 클래스
public abstract class Item {
@Id @GeneratedValue
@Column(name="item_id")
private Long id;
...
}
@Entity
public class Album extends Item { ... }
@Entity
public class Movie extends Item { ... }
🧷 장점
- 서브 타입을 구분해서 처리할 때 효과적이다.
- not null 제약조건을 사용할 수 있다.
🧷 단점
- 자식 테이블을 통합해서 쿼리하기 어렵다.
- 여러 자식 테이블을 함께 조회할 때 성능이 느리다. (UNION 사용)
🧷 특징
- 구분 칼럼을 사용하지 않는다.
📌 @MappedSuperclass
부모 클래스를 상속 받는 자식 클래스에게 매핑 정보만 제공하고 싶을 때 사용한다.
물려받은 매핑 정보를 수정하려면 @AttributeOverride 혹은 @AttributeOverrides를 사용한다.
연관관계를 재정의하려면 @AssociationOverride 혹은 @AssociationOverrides를 사용한다.
매핑 정보를 모아주는 역할일 뿐 ORM에서 이야기하는 진정한 상속은 슈퍼-서브타입 매핑이다.
📌 복합 키와 식별 관계 매핑
1️⃣ 식별 관계 vs 비식별 관계
- 식별 관계 : 부모 테이블의 기본 키를 받아 자식 테이블이 기본 키 + 외래 키를 사용하는 관계
- 비식별 관계 : 부모 테이블의 기본 키를 받아 자식 테이블의 외래 키로만 사용하는 관계
2️⃣ 복합 키 : 비식별 관계 매핑
JPA에서 식별자를 둘 이상 사용하려면 별도의 식별자 클래스를 만들어야 한다.
식별자를 equals와 hashCode로 구분하기 때문에 구현한 식별자 클래스에 두 메소드를 구현해야 한다.
🌀IdClass
관계형 데이터베이스에 가까운 복합키 방식이다.
@Entity
@IdClass(ParentId.class) // ParentId라는 식별자 클래스로 구분
public class Parent { // 부모 엔티티
@Id
@Column(name = "PARENT_ID1")
private String id1; // ParentId.id1과 연결
@Id
@Column(name = "PARENT_ID2")
private String id2; // ParentId.id2과 연결
}
부모 엔티티에 @IdClass로 사용할 식별자 클래스를 지정해준다.
public class ParentId implements Serializable {
private String id1;
private String id2;
public ParentId(String id1, String id2) {
this.id1 = id1;
this.id2 = id2;
}
@Override
public boolean equals(Object o) {...}
@Override
public int hashCode() {...}
}
@IdClass를 사용하는 식별자 클래스는 다음 조건을 꼭 만족해야 한다.
- 식별자 클래스의 속성명과 엔티티에서 사용하는 식별자의 속성명이 같아야 한다.
- Serializable 인터페이스를 구현해야 한다.
- equals, hashCode를 구현해야 한다.
- 기본 생성자가 있어야 한다.
- 식별자 클래스는 public이어야 한다.
자식 클래스 추가는 다음과 같다.
@Entity
public Class Childe {
@Id
private String id;
@ManyToOne
@JoinColumns({ // 부모의 키를 모두 외래 키로 등록
@JoinColumn(name = "PARENT_ID1",
referencedColumnName = "PARENT_ID1"),
@JoinColumn(name = "PARENT_ID2",
referencedColumnName = "PARENT_ID2")})
private Parent parent;
}
🌀 EmbeddedId
좀 더 객체지향적으로 복합 키를 매핑하는 방법이다.
@Entity
public class Parent {
@EmbeddedId
private ParentId id;
...
}
부모 엔티티에 식별자 클래스를 직접 사용하고 @Embedded 어노테이션을 적어주면 된다.
@Embeddable
public class ParentId implements Serializable {
@Column(name = "PARENT_ID1")
private String id1;
@Column(name = "PARENT_ID2")
private String id2;
// equals & hashCode 구현
}
@EmbeddedId를 사용하는 식별자 클래스는 다음 조건을 만족해야 한다.
- @Embeddedable 어노테이션을 붙여야 한다.
- Serializable 인터페이스를 구현해야 한다.
- equals, hashCode를 구현해야 한다.
- 기본 생성자가 있어야 한다.
- 식별자 클래스는 public이어야 한다.
🧐 왜 equals()와 hashCode()를 구현해야 할까??
영속성 컨텍스트는 엔티티의 식별자를 키로 사용해서 엔티티를 관리한다.
키를 비교할 때 equals()와 hashCode()를 사용하는데, 객체를 대상으로 equals()를 사용하면 동일성 비교(참조 비교)가 이루어지기 때문이다.
따라서, 오버라이딩을 하지 않으면 예상과 다른 엔티티를 찾게 되는 등의 문제가 발생한다.
3️⃣ 복합 키 : 식별 관계 매핑
비식별 관계와 마찬가지로 @IdClass와 @EmbeddedId를 사용해서 식별자를 매핑해야 한다.
🌀 IdClass
@Entity // 부모 엔티티
public class Parent {
@Id @Colum(name = "PARENT_ID")
private String id;
}
@Entity // 자식 엔티티
@IdClass(ChildId.class)
public class Child {
@Id
@ManyToOne
@JoinColumn(name = "PARENT_ID")
public Parent parent;
@Id
@Column(name = "CHILD_ID")
private String childId;
}
// 자식 식별자 클래스
public class ChildId implements Serializable {
private String parent;
private String childId;
// equals, hashCode
}
Childe 엔티티는 @Id, @ManyToOne, 그리고 @JoinColumn을 통해서 기본 키와 외래 키를 같이 매핑한다.
@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;
}
// 손자 식별자 클래스
public class GrandChildId implements Serializable {
private Child child;
private String id;
// equals, hashCode
}
🌀 EmbeddedId
@Entity // 부모 클래스
public class Parent {
@Id @Column(name = "PARENT_ID")
private String id;
}
@Entity // 자식 엔티티
public class Child {
@EmbeddedId
private ChildId id;
@MapsId("parentId")
@ManyToOne
@JoinColumn(name = "PARENT_ID")
public Parent parent;
}
@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")
@ManyToOne
@JoinColumns({{
@JoinColumn(name = "PARENT_ID"),
@JoinColumn(name = "CHILD_ID")})
private Child child;
}
@Embeddable // 손자 식별자 클래스
public class GrandChildId implements Serializable {
private ChildId childId;
@Column(name = "GRANDCHILD_ID")
private String id;
// equals, hashCode
}
'개인 공부 > JPA (자바 ORM 표준 JPA 프로그래밍)' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 다양한 연관관계 매핑 (6장) (0) | 2022.12.02 |
---|---|
자바 ORM 표준 JPA 프로그래밍 : 연관관계 매핑 기초 (5장) (0) | 2022.11.18 |
자바 ORM 표준 JPA 프로그래밍 : 엔티티 매핑 (4장) (1) | 2022.10.06 |
자바 ORM 표준 JPA 프로그래밍 : 영속성 관리 (3장) (0) | 2022.09.26 |
자바 ORM 표준 JPA 프로그래밍 : JPA 시작 (2장) (0) | 2022.09.15 |
댓글