RDB
에는 객체지향과 다르게 상속이라는 개념이 없다. 대신에 이와 비슷한 슈퍼 타입
과 서브 타입
관계라는 모델링 기법을 이용한다. ORM
에서의 상속 관계 매핑
은 DB의 슈퍼/서브타입
관계를 매핑하는 것이다.
슈퍼 타입 서브 타입
논리 모델을 실제 물리 모델인 테이블로 구현할 때는 3가지
방법이 있다.
각각을 모두 테이블로 만들고 조회할 때 JOIN
을 사용한다.
JPA
에서는 이를 조인 전략이라고 말한다.
테이블을 하나만 사용해서 통합
한다.
JPA
에서는 단일 테이블 전략이라고 한다.
서브 타입
마다 하나의 테이블을 만든다.
JPA
에서는 구현 클래스마다의 테이블 전략이라고 한다.
조인 전략은, 엔티티 각각을 모두 테이블로 만들고 자식 테이블이 부모 테이블의 기본 키를 받아서 기본 키
+ 외래 키
로 사용하는 전략이다. 따라서 조회할 때, JOIN
을 많이 사용한다.
단, 해당 전략을 사용할 때 주의할 점이 있는데 객체는 타입
으로 구분할 수 있지만, 테이블은 타입의 개념이 없다.
따라서 타입을 구분하는 구분 컬럼
을 추가해야 한다.
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {
@Id
@GeneratedValue
private Long id;
private String name;
private int price;
... get, set
}
상속 매핑은 부모 클래스에 @Inheritance
어노테이션을 붙여야 한다. 그리고 여러 매핑 전략이 있는데 InheritanceType.JOINED
은 조인 전략이다.
부모 클래스에 구분 컬럼
을 지정한다. 기본값은 DTYPE
이지만, name=이름
을 이용해서 컬럼 이름을 바꿀 수 없다. 이외의 나머지 속성들의 종류는 @Column
의 속성들과 동일하다.
@Entity
@DiscriminatorValue("B")
@PrimaryKeyJoinColumn(name = "BOOK_ID") // id 값 변경
public class Book extends Item {
private String author;
private String isbn;
... get, set
}
엔티티를 저장할 때 구분 컬럼
에 입력할 값을 지정한다. 만약 Book 엔티티를 저장하면 구분 컬럼인 DTYPE
에 "B"
이 저장된다.
기본값으로 자식 테이블은 부모 테이블의 ID 컬럼명을 그대로 사용한다. 그러나 만약 자식 테이블의 기본 키 컬럼명을 변경하고 싶으면 @PrimaryKeyJoinColumn
을 사용하면 된다.
INSERT SQL
을 두 번
실행한다. (Item / 자식 한 번씩)JPA 표준 명세는 구분 컬럼
을 사용하도록 권장하지만, 하이버네이트를 포함한 몇몇 구현체는 구분 컬럼
없이도 잘 동작한다. 조인 전략
이 이에 해당된다.
단일 테이블 전략은, 이름 그대로 테이블을 하나만 사용한다. 그리고 구분 컬럼
으로 어떤 자식 데이터가 저장되었는지 구분한다. 조회할 때 조인을 사용하지 않으므로 일반적으로 가장 빠르다.
이 전략을 사용할 때 주의점은 자식 엔티티를 매핑 한 컬럼은 모두 null
을 허용해야 한다. 왜냐하면 단일 테이블 전략
이기에 연관된 클래스의 필드들도 컬럼이 되기 때문이다.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {
@Id
@GeneratedValue
private Long id;
private String name;
private int price;
... get, set
}
상속 매핑은 부모 클래스에 @Inheritance
어노테이션을 붙여야 한다. 그리고 여러 매핑 전략이 있는데 InheritanceType.SINGLE_TABLE
은 단일 테이블 전략이다. 참고로, 해당 어노테이션을 생략해도 기본적으로 단일 테이블 전략
이 수행된다.
부모 클래스에 구분 컬럼
을 지정한다. 기본값은 DTYPE
이지만, name=이름
을 이용해서 컬럼 이름을 바꿀 수 없다. 이외의 나머지 속성들의 종류는 @Column
의 속성들과 동일하다. 참고로, SINGLE_TABLE
은 구분 컬럼이 필수적이다. 그렇기에 해당 어노테이션이 없어도 이를 자동으로 호출하여 사용한다.
@Entity
@DiscriminatorValue("B")
public class Book extends Item {
private String author;
private String isbn;
...get, set
}
이 전략은 데이터베이스 설계자와 ORM 전문가 둘 다 추천 ❌
생성일
, 수정일
을 등록해야 하는 경우 (createdAt
, updatedAt
)@Entity
클래스는 엔티티나 @MappedSuperclass
로 지정한 클래스만 상속 가능@MappedSuperclass
는 단순히 하위 클래스에게 매핑 정보
만 넘겨주는 어노테이션이다.
즉, 단순히 매핑 정보
를 상속할 목적으로만 사용된다.
@MappedSuperclass
public class BaseEntity {
private String createdBy;
private LocalDateTime createDate;
private String lestModifiedBy;
private LocalDateTime lastModifiedDate;
}
BaseEntity
에는 주로 사용하는 공통 매핑 정보를 정의한다. @MappedSuperclass
에는 따로 속성값은 없다.
@Entity
public class Team extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "TEAM_ID")
private Long id;
@Column(name = "TEAM_NAME")
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
... get, set
}
매핑 정보를 물려받을 클래스는 extends
를 통해 @MappedSuperclass
를 정의한 클래스를 상속받는다.
만약, 부모로부터 물려받은 매핑 정보를 재정의하고 싶다면 @AttributeOverrides
나 @AttributeOverride
를 사용하면 된다.
@Entity
@AttributeOverride(name = "createdBy", column = @Column(name = "TEAM_CREATED_BY"))
public class Team extends BaseEntity { ... }
@Entity
@AttributeOverrides({
@AttributeOverride(name = "createdBy", column = @Column(name = "TEAM_CREATED_BY")),
@AttributeOverride(name = "createDate", column = @Column(name = "TEAM_CREATE_DATE"))
})
public class Team extends BaseEntity { ... }