상속 관계 매핑
객체는 다형성을 활용한 상속 개념이 존재한다. 그러나 DB에는 상속관계란 존재하지 않는다.
그나마 DB에는 슈퍼타입 - 서브타입으로 객체의 상속과 유사하게 사용할 수는 있다.
JPA는 이를 활용하여 객체의 상속 관계를 DB의 슈퍼타입 + 서브타입 관계로 매핑할 수 있도록 지원한다.
상속 관계 매핑 전략
1. 조인전략
조인전략이란 말 그래도 모든 테이블을 나누고, 이를 조인으로 데이터를 가져오는 전략이다.
만약 Album 객체 데이터를 저장하면, 이름 + 가격은 ITEM 테이블에 입력되고 ARTIST 정보는 ALBUM 테이블에 입력이된다. 즉 총 두번의 insert문이 나간다. (가져올 때는 FK를 조인하여 가져옴)
추가적으로 DTYPE이라는 컬럼은 어떤 ITEM인지 구분하기 위한 구분자 역할을 한다.
ITEM 테이블의 PK를 BOOK 테이블에서 FK이자 PK로 사용한다.
추가적으로 flush + clear후 실제 DB에서 데이터를 가져오면 다음처럼 테이블을 조인하여 데이터를 가져온다
가장 테이블이 정규화가 잘 되어있고, 외래키 참조 무결성 제약조건을 활용할 수 있다.
그리고 정규화가 잘 되어있기 때문에 저장공간에 효율적이다.
하지만 이 전략은 결국 조인을 많이 사용해야하고, 조회 쿼리가 복잡하다는 단점이 있다. 결정적으로 두 테이블에 Insert 쿼리를 날려야한다는 단점도 존재한다.
2. 단일 테이블 전략
단일 테이블 전략은 그냥 한 테이블에 다 때려박고, 필요없는 값은 null로 처리하면 된다.
여기서도 DTYPE은 아이템 종류를 구분하기 위한 컬럼이다.
싱글 테이블 전략은 그냥 한 테이블에 모든 컬럼을 때려박고, 빈 곳은 null로 유지한다.
단일 테이블 전략은 조인이 필요가 없으므로 하나의 테이블만 찾으면 되니 조회 성능이 빠르고 쿼리가 단순하다.
하지만 null을 허용해야하고, 테이블이 무거워져서 유지보수 및 조회가 느려질 수 있다는 단점도 존재한다.
3. 구현 클래스별 테이블 생성 전략
마지막 방법은 정말 단순하게, 그냥 ITEM + 자식 클래스를 합친 하나의 새로운 테이블로 각각의 새로운 테이블을 생성하는 방식이다.
굳이 예시는 첨부하지 않겠다.
위 방법들중 가장 비추천적인 방식이다.
모든 테이블을 나누는 방법으로써, 서브 타입을 명확하게 구분할 수 있다는 큰 장점이 있지만 (not null도 가능), 결국 여러 자식 테이블을 엮어서 조회하게 되면 반드시 조인이 필요함으로써 성능이 구리다.
전략을 선택하는 방법
먼저 코드를 보자.
@Entity
@Inheritance(strategy = InheritanceType.여기에 원하는 전략)
@DiscriminatorColumn
@Getter @Setter
public abstract class Item {
@Id @GeneratedValue
private Long id;
private String name;
private int price;
}
@Inheritance
어떤 방법으로 구현할 것인지는 이 역시 에노테이션을 사용하면 된다.
@Inheritence 에노테이션 : 디폴트는 싱글테이블 전략
세가지 전략도 미리 enum으로 제공한다.
@DiscriminatorColumn
이 에노테이션을 넣으면 서브 타입을 구분하기 위한 DTYPE 컬럼이 추가되게 된다.
기본적으로 컬럼 이름이 DTYPE으로 들어간다. 맘에 안들면 옵션으로 이름 변경 가능 (item_type 이렇게 넣어도 됨)
@DiscriminatorValue(“XXX”)
서브 타입에 붙이는 에노테이션으로써, DTYPE에 표시될 값을 지정하는 에노테이션이다.
@Entity
@Getter
@Setter
@DiscriminatorValue("A")
public class Album extends Item {
private String artist;
}
기본 값은 클래스명이고, 위처럼 ALBUM에 A라는 ALIAS를 줄 수 있다.
테스트코드 작성
만약 볼빨간 사춘기 앨범을 저장하면, 다음과 같이 데이터가 들어갈 것이다. (상속 전략은 JOINED로 테스트 진행)
@Test
public void insertAlbumTest() throws Exception {
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Album album = new Album();
album.setName("우주를 줄게");
album.setArtist("볼빨간 사춘기");
em.persist(album);
em.flush();
em.clear();
Album findAlbum = em.find(Album.class, album.getId());
System.out.println("findAlbum = " + findAlbum.getName() + " by = " + findAlbum.getArtist());
tx.commit();
} catch(Exception e) {
tx.rollback();
} finally {
em.close();
}
}
insert 두방 나감
DTYPE 으로 A가 나가고, 데이터는 조인해서 찾아오는 것을 볼 수 있다.
결론
조인 전략이 아무래도 정규화가 잘 되어 있기 때문에, 기본적으로는 조인 전략을 사용하자.
다만 데이터도 별로 없고 확장성도 없고 간단하다면 단일 테이블 전략을 선택하자.
구현 클래스별 테이블 생성 전략은 쳐다도 보지 말자.