관계형 데이터 베이스는 상속관계가 없다.
하지만 슈퍼타입, 서브타입이라는 모델링 기법이 존재한다.
이를 이용하여 객체의 상속 관계와 RDB의 슈퍼타입, 서브타입 관계를 매핑할 수 있다.
JPA는 상속 관계를 어떻게 매핑하는지 알아보기전에 기본 매핑전략으로 어떤 전략을 채용하는지 알아보자.
@Entity
public abstract class Item {
@Id @GeneratedValue
private Long id;
private String name;
private int price;
}
@Entity
public class Book extends Item {
private String author;
private String isbn;
}
@Entity
public class Movie extends Item{
private String diresctor;
private String actor;
}
@Entity
public class Album extends Item{
private String artist;
}
실행 결과:
Hibernate:
create table Item (
price integer not null,
id bigint not null,
DTYPE varchar(31) not null,
actor varchar(255),
artist varchar(255),
author varchar(255),
diresctor varchar(255),
isbn varchar(255),
name varchar(255),
primary key (id)
)
JPA의 기본전략은 단일 테이블 방식인것을 확인할 수 있다.
JPA는 상속관계를 조인, 단일 테이블, 구현 클래스별 테이블 전략 3가지로 매핑할 수 있다.
이를 구현 하기 위한 애노테이션들은 다음과 같다.
@Inheritance(strategy=InheritanceType.XXX)
는 슈퍼 타입에서 매핑 전략을 구분하기 위한 애노테이션이다.
@DiscriminatorColumn()은 구분자 컬럼을 말한다.
@DiscriminatorValue(“XXX”)은 구분자 컬럼의 값을 정할 수 있다.

@Inheritance(strategy = InheritanceType.JOINED)를 사용해서 구현 가능하다.
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
public abstract class Item {
@Id @GeneratedValue
private Long id;
private String name;
private int price;
}
@Entity
@DiscriminatorValue("M")
public class Movie extends Item{
private String diresctor;
private String actor;
}
Movie movie = new Movie();
movie.setDiresctor("aaa");
movie.setActor("bbb");
movie.setName("JPA");
movie.setPrice(10000);
em.persist(movie);
em.flush();
em.clear();
Movie findMove = em.find(Movie.class, movie.getId());
System.out.println("findMove = " + findMove.getName());
실행 결과:
Hibernate:
create table Album (
id bigint not null,
artist varchar(255),
primary key (id)
)
Hibernate:
create table Book (
id bigint not null,
author varchar(255),
isbn varchar(255),
primary key (id)
)
Hibernate:
create table Movie (
id bigint not null,
actor varchar(255),
diresctor varchar(255),
primary key (id)
)
Hibernate:
create table Item (
price integer not null,
id bigint not null,
DTYPE varchar(31) not null,
name varchar(255),
primary key (id)
)
Hibernate:
/* insert for
hellojpa.Movie */insert
into
Item (name, price, DTYPE, id)
values
(?, ?, 'M', ?)
Hibernate:
/* insert for
hellojpa.Movie */insert
into
Movie (actor, diresctor, id)
values
(?, ?, ?)
Hibernate:
select
m1_0.id,
m1_1.name,
m1_1.price,
m1_0.actor,
m1_0.diresctor
from
Movie m1_0
join
Item m1_1
on m1_0.id=m1_1.id
where
m1_0.id=?
조인 전략은 외래키 참조 무결성 제약 조건을 활용하여 테이블을 정규화할 수 있기 때문에 저장공간을 효울적으로 사용할 수 있다.
하지만 데이터 조회시 조인을 하기 떄문에 상황에 따라 성능 문제가 생길수도 있다.(실무에서는 조일할 테이블이 많을것이므로 조회 쿼리가 복잡할수도 있다.)
또한 테이블이 정규화됨에 따라 슈퍼타입 테이블과 서브타입 테이블이 나눠져 있기 때문에 INSERT SQL이 2번 호출되기 성능 문제가 생길 수도 있다.

@Inheritance(strategy = InheritanceType.SINGLE_TABLE) 사용
단일 테이블 전략에서는 DTYPE가 없으면 어떤 하위 객체인지 확인할 수 없으므로 기본적으로 DTYPE을 생성하므로 @DiscriminatorColumn가 없어도 DTYPE이 생성된다.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
//@DiscriminatorColumn
public abstract class Item {
@Id @GeneratedValue
private Long id;
private String name;
private int price;
}
@Entity
@DiscriminatorValue("A")
public class Album extends Item{
private String artist;
}
Album album = new Album();
album.setArtist("JPA");
em.persist(album);
em.flush();
em.clear();
Album findAlum = em.find(Album.class, album.getId());
System.out.println("findAlum.getArtist() = " + findAlum.getArtist());
실행결과:
Hibernate:
create table Item (
price integer not null,
id bigint not null,
DTYPE varchar(31) not null,
actor varchar(255),
artist varchar(255),
author varchar(255),
diresctor varchar(255),
isbn varchar(255),
name varchar(255),
primary key (id)
)
Hibernate:
/* insert for
hellojpa.Album */insert
into
Item (name, price, artist, DTYPE, id)
values
(?, ?, ?, 'A', ?)
Hibernate:
select
a1_0.id,
a1_0.name,
a1_0.price,
a1_0.artist
from
Item a1_0
where
a1_0.DTYPE='A'
and a1_0.id=?
findAlum.getArtist() = JPA
단일 테이블 전략은 기본적으로 조회할때 조인을 하지않으므로 조회 성능이 빠르고 단순하다.(조회 성능 임계점을 초과하지 않을떄까지) 하지만 자식 엔티티가 매핑한 컬럼중 쓰이지 않는 자식 엔티티들은 모두 null을 허용하는 문제가 있다.

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) 사용
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Item {
@Id @GeneratedValue
private Long id;
private String name;
private int price;
}
@Entity
@DiscriminatorValue("B")
public class Book extends Item {
private String author;
private String isbn;
}
```log
Hibernate:
create table Book (
price integer not null,
id bigint not null,
author varchar(255),
isbn varchar(255),
name varchar(255),
primary key (id)
)
Hibernate:
/* insert for
hellojpa.Book */insert
into
Book (name, price, author, isbn, id)
values
(?, ?, ?, ?, ?)
Hibernate:
select
b1_0.id,
b1_0.name,
b1_0.price,
b1_0.author,
b1_0.isbn
from
Book b1_0
where
b1_0.id=?
findBook.getAuthor() = aaa
클래스별 테이블 전략은 서브타입을 명확하게 구분해서 처리할수 있기때문에 null을 허용하지 않고 사용할 수 있다.
하지만 자식 테이블들을 통합해서 조회할 상황이 온다면 조회 성능도 느리고 통합해서 쿼리를 사용하기도 힘들다.
간단하게 생각해봐도 서브타입 테이블들에 대한 집계 연산을 할떄 어려움이 있으므로 실무에서 사용하기 어려운 전략인 것 같다.

@MappedSuperclass
public abstract class BaseEntity {
private String createBy;
private LocalDateTime createdDate;
private String lastModifiedBy;
private LocalDateTime lastModifiedDate;
}
@Entity
public class Member extends BaseEntity{
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
}
Member member = new Member();
member.setUsername("JPA");
member.setCreateBy("kim");
member.setCreatedDate(LocalDateTime.now());
em.persist(member);
em.flush();
em.clear();
Member findMember = em.find(Member.class, member.getId());
System.out.println("findMember.getCreateBy() = " + findMember.getCreateBy());
실행 결과:
Hibernate:
create table Member (
MEMBER_ID bigint not null,
TEAM_ID bigint,
createdDate timestamp(6),
lastModifiedDate timestamp(6),
team_TEAM_ID bigint,
USERNAME varchar(255),
createBy varchar(255),
lastModifiedBy varchar(255),
primary key (MEMBER_ID)
)
Hibernate:
/* insert for
hellojpa.Member */insert
into
Member (createBy, createdDate, lastModifiedBy, lastModifiedDate, USERNAME, MEMBER_ID)
values
(?, ?, ?, ?, ?, ?)
Hibernate:
select
m1_0.MEMBER_ID,
m1_0.createBy,
m1_0.createdDate,
m1_0.lastModifiedBy,
m1_0.lastModifiedDate,
t1_0.TEAM_ID,
t1_0.name,
m1_0.USERNAME
from
Member m1_0
left join
Team t1_0
on t1_0.TEAM_ID=m1_0.team_TEAM_ID
where
m1_0.MEMBER_ID=?
findMember.getCreateBy() = kim
@MappedSuperclass은
엔티티의 반복되는 속성을 같이 쓰고 싶을때 사용한다.(공통 속성은 상속받은 엔티티의 테이블에 추가된다)
테이블과 관계 없고, 단순히 엔티티가 공통으로 사용하는매핑 정보를 모으는 역할을 한다.
주로 등록일, 수정일, 등록자, 수정자 같은 전체 엔티티에서 공통으로 적용하는 정보를 모을 때 사용한다.
조회, 검색(em.find(BaseEnitity.class))이 불가능하다.
직접 생성해서 사용할 일이 없지만 누군가는 실수할 수 있으니 미리 방지하기 위해 추상 클래스 권장한다.
단일 테이블 전략과 유사하므로 헷갈릴수도 있지만 @MappedSuperclass는 테이블과 관련이 없다는것만 기억하자.
참고:@Entity 클래스는 @Entity,@MappedSuperclass만 상속 가능하다.