1. 상속 관계 매핑


객체 모델에서 상속관계는 아주 흔합니다. 그런데 관계형 DB는 상속관계가 없는데 어떻게 해야 할까요?
관계형 DB에는 슈퍼타입-서브타입 관계라는 모델링 기법이 있는데, 이 모델로 객체의 상속관계와 매핑할 수 있습니다.

[그림 1] 슈퍼/서브 타입 예시 논리 데이터 모델

위 슈퍼/서브 타입 논리 모델을 가지고 설명해보겠습니다.
우선, 슈퍼/서브 타입 모델은 논리적인 데이터 모델에서만 이용되는 형태이기 때문에 물리적인 데이터 모델로 변환 작업이 필요합니다.
슈퍼/서브 타입 논리 모델을 물리 모델로 변환하는데에는 3가지 방법이 있습니다.

  1. 각각 테이블로 변환(JOINED 전략) - 위 형태 그대로 테이블을 만들어 item_id 와 같은 컬럼으로 JOIN하는 방법
  2. 통합 테이블로 변환(SINGLE_TABLE 전략) - 위의 예에서 ITEM 테이블에 음반, 영화, 책에 해당되는 컬럼을 모두 다 때려박는 방법
  3. 서브 타입 테이블로 변환(TABLE_PER_CLASS 전략) - 각 서브 테이블에 '물품'에 있을 컬럼을 각각 다 넣어주는 방법

 

JPA는 위 3가지 방법을 모두 지원하고, 디폴트 전략은 SINGLE_TABLE 전략입니다.
위 세 가지 전략을 그림으로 표현하면 다음과 같습니다.

[그림 2] 각 전략별 테이블 형태

Entity 클래스로는 다음과 같이 extends 키워드를 사용해 상속합니다.

@Entity
public class Item { ... }

@Entity
public class Album extends Item { ... }
@Entity
public class Movie extends Item { ... }
@Entity
public class Book extends Item { ... }

위와 같이 작성한 후, 실행해보면 SINGLE_TABLE 전략이 디폴트이기 때문에 ITEM 테이블에 Album, Movie, Book에서 사용되는 컬럼들이 같이 들어가있는 형태로 생성됩니다. 그리고 추가로 DTYPE 이라는 컬럼이 추가되는데, 이는 Album인지 Movie인지 Book인지 구분하기 위한 컬럼입니다. 전략을 바꿔주고 싶다면 다음과 같이 @Inheritance 애너테이션과 strategy 속성을 사용합니다.

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Item { ... }

그리고 위에서 Album인지 Movie인지 Book인지 구분하기 위한 DTYPE 이라는 컬럼이 추가된다고 했는데요, DTYPE은 JPA에서 정한 디폴트 컬럼명입니다. DTYPE 이라는 컬럼명 대신 다른 이름을 사용하고 싶다면 @DiscriminatorColumn 을 사용합니다.
(하지만 관례상 굳이 수정하지 마시고 그냥 두는게 좋습니다.)

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "sub_type")
public class Item { ... }

위와 같이 명시하면 Item 테이블에 DTYPE 이라는 이름대신 sub_type이라는 컬럼명이 들어갑니다. 여기에 세팅되는 값으로는 서브 Entity 클래스명이 디폴트로 들어오게 됩니다. 해당 예에서는 Album, Movie, Book 이라는 값이 들어가게 됩니다. 만약 이 값을 바꿔주고 싶다면 서브 Entity 클래스에 @DiscriminatorValue 를 명시하면 됩니다. (마찬가지로 굳이 수정하는거보다 놔두는게 좋습니다.)

@Entity
@DiscriminatorValue("A")
public class Album { ... }

2. @MappedSuperclass란?


위 상속 관계 매핑은 DB 테이블 모델과 매핑하는 방법이었다면, @MappedSuperclass는 DB 테이블은 신경쓰지 않고 오로지 객체 관점에서 공통된 기능을 따로 클래스로 만들고 상속해서 쓰고싶을 때 사용합니다. 다음 코드를 살펴보겠습니다.

@MappedSuperclass
public class BaseEntity {
	private String createdBy;
	private LocalDateTime createdDate;
	private String lastModifiedBy;
	private LocalDateTime lastModifiedDate;
}

@Entity
public class Member extends BaseEntity { ... }

위 코드를 실행하면 Member 테이블이 생성되고, Member Entity 클래스 내에 명시된 필드뿐만 아니라, 상속받은 BaseEntity 클래스 내에 있는 필드도 같이 Member 테이블의 컬럼으로 생성됩니다.

 

요약하면 다음과 같습니다.

  • DB 테이블 모델과 매핑되는 상속관계 매핑이 아닙니다.
  • 해당 애너테이션이 있는 클래스는 Entity가 아닙니다. 따라서, DB 테이블로 생성되지 않습니다.
  • 테이블과 관계없이 단순히 Entity가 공통으로 사용하는 매핑 정보를 모아논 클래스입니다.
    주로 등록일, 수정일, 등록자, 수정자와 같은 전체 Entity에서 공통으로 적용하는 정보를 모을때 사용합니다.
  • 직접 생성해서 쓸 일이 없으므로 추상 클래스로 만들도록 권장됩니다.
  • 참고로 Entity 클래스는 같은 Entity 클래스나 @MappedSuperclass가 붙은 클래스만 상속받을 수 있습니다.