상속 관계 매핑
데이터베이스에는 테이블 간의 상속 관계라는 개념이 없다.
굳이 비슷한 개념을 찾자면 슈퍼 타입과 서브 타입이라는 개념이 존재한다.
슈퍼 타입과 서브 타입이라는 개념도 논리 모델의 개념이며, 이것을 물리 모델로 구현하는 방법에는 세 가지가 있다.
- 조인 전략
- 단일 테이블 전략
- 개별 테이블 전략
세 가지 구현 방법에 따라 실제 테이블 설계가 달라지며 JPA는 객체, 즉 엔티티와 각 테이블 설계를 매핑하는 방법을 제공한다.
정리하자면 데이터베이스의 슈퍼 타입과 서브 타입을 구현 하는 방법들과 상속 관계를 가진 엔티티들을 매핑하는 것이 상속 관계 매핑이다.
조인 전략
조인 전략은 객체 지향 설계와 가장 비슷하게 슈퍼타입과 서브타입을 구현하는 방법이다.
조인 전략을 사용할 경우, 오른쪽과 같이 테이블이 구현된다.
슈퍼 타입인 ITEM 테이블이 서브 타입인 세 테이블의 공통 컬럼을 가진다. 그리고 서브 타입 테이블들은 각각의 고유한 컬럼을 가진다.
서브 타입들은 슈퍼 타입의 PK를 FK로 가진다. 그리고 FK에 유니크 제약 조건을 걸어 PK의 역할도 함께 한다.
Item, Album, Movie, Book은 엔티티이며, Item은 나머지 엔티티들이 상속하는 상위 엔티티이다. 전형적인 클래스 간의 상속 관계이다.
왼쪽의 엔티티 상속 관계를 오른쪽 데이터베이스 테이블 설계에 매핑하는 방법은 아래 코드와 같다.
@Inheritance(strategy = InheritanceType.JOINED)를 상위 클래스, 즉 슈퍼 타입에 해당하는 엔티티에 적용해준다.
strategy = InheritanceType.JOINED는 조인 전략으로 설계된 테이블에 매핑한다는 의미이다.
@DiscriminatorColum은 슈퍼 타입에서 서브 타입의 타입을 구별할 수 있는 DTYPE 컬럼을 매핑하는 어노테이션이다.
그리고 서브 타입에 해당하는 엔티티들은 슈퍼 타입에 해당하는 엔티티를 상속하면 된다.
// Item만 단독으로 저장할 일이 없다고 판단 -> 추상 클래스
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
public abstract class Item extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "item_id")
private Long id;
private String name;
private int price;
...
}
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
@Entity
public class Album extends Item {
private String artist;
private String ect;
...
}
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
@Entity
public class Movie extends Item {
private String director;
private String actor;
...
}
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
@Entity
public class Book extends Item {
private String author;
private String isbn;
...
}
단일 테이블 전략
단일 테이블 전략은 오른쪽에 보이는 것 처럼 슈퍼 타입이 공통 컬럼과 서브 타입들의 고유 컬럼을 모두 가지는 구현 방법이다.
왼쪽의 엔티티 상속 관계를 오른쪽 데이터베이스 테이블 설계에 매핑하는 방법은 아래의 코드와 같다.
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)을 슈퍼 타입에 해당하는 엔티티에 적용해주면 된다.
나머지는 조인 전략과 동일하다.
한 가지 염두할 점은 단일 테이블 전략 매핑을 할 경우, @DiscriminatorColum이 자동으로 적용된다는 것이다.
// Item만 단독으로 저장할 일이 없다고 판단 -> 추상 클래스
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn // 생략해도 자동 적용
public abstract class Item extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "item_id")
private Long id;
private String name;
private int price;
...
}
개별 테이블 전략
개별 테이블 전략은 슈퍼 타입에 해당하는 테이블은 생성하지 않고, 서브 타입에 해당하는 테이블만 생성하는 구현 방법이다.
해당 구현 방법을 사용할 때는 슈퍼 타입을 반영하는 동일한 이름의 PK를 사용하는 것이 좋다.
왼쪽의 엔티티 상속 관계를 오른쪽 데이터베이스 테이블 설계에 매핑하는 방법은 아래의 코드와 같다.
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)을 슈퍼 타입에 해당하는 엔티티에 적용해주면 된다.
주의할 점은 슈퍼 타입 테이블은 생성되지 않으므로 해당하는 엔티티는 반드시 추상 클래스여야 한다.
그리고 @DiscriminatorColumn도 사용할 필요가 없다(사용해도 오류가 발생하지는 않지만, 실제로 적용되지 않음).
마찬가지로 나머지는 조인 전략, 단일 테이블 전략과 동일하다.
개별 테이블 전략은 추천되지 않는 방법이다.
상위 클래스인 Item타입을 통해 특정 PK의 데이터를 조회할 때, union all을 통해 모든 서브 타입 테이블을 조회한다. 따라서 매우 비효율적이다.
// Item만 단독으로 저장할 일이 없다고 판단 -> 추상 클래스
// 개별 테이블 전략(TABLE_PER_CLASS)을 사용할 경우, 반드시 상위 엔티티는 추상 클래스여야 한다.
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Item extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "item_id")
private Long id;
private String name;
private int price;
...
}