[JPA] 상속관계 매핑 (조인 전략, 단일테이블 전략, 구현클래스마다 테이블 전략)
안녕하세요!
키크니 개발자 입니다. 🦒
오늘은 상속관계 매핑에 대해서 알게 되어서 정리해보았습니다.
항상 JPA는 @ManyToOne, @OneToMany으로 연관관계만 알고있었는데
상속관계에 대해서도 매핑을 할 수 있다는 점을 처음알게되었습니다.
상속관계에서의 매핑은?
- 관계형 데이터베이스는 상속 관계가 없습니다.
- 슈퍼타입, 서브타입 관계라는 모델링 기법이 객체 상속과 유사하다고 볼 수 있습니다.
- 상속관계 매핑은 객체의 상속과 구조와 DB의 슈퍼타입, 서브타입 관계를 매핑합니다.
- 크게는 조인 전략, 단일테이블 전략, 구현클래스마다 테이블 전략으로 볼 수 있습니다.
슈퍼타입, 서브타입 논리 모델을 실제 물리 모델로 구현하는 방법
- 각각 테이블로 변환 -> 조인 전략
- 통합 테이블로 변환 -> 단일 테이블 전략
- 서브타입 테이블로 변환 -> 구현 클래스마다 테이블 전략
아래와 같이 Item을 상속받게 된채로 아무것도 설정을 하지 않고 실행을 하게 되면,
@Entity
public abstract 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 Album extends Item {
private String artist;
}
@Entity
public class Movie extends Item {
private String director;
private String actor;
}
아래와 같이 단일 테이블(Item)에서 모든 컬럼들이 생성되는 것을 확인할 수 있습니다.
create table item (
dtype varchar(31) not null,
id bigint not null,
name varchar(255),
price integer not null,
artist varchar(255),
author varchar(255),
isbn varchar(255),
actor varchar(255),
director varchar(255),
primary key (id)
)
이 이유는 기본적으로 Inheritance가 SINGLE_TABLE로 설정되어있기 때문입니다.
조인 전략
조인전략을 정석이라고 생각하고 테이블을 구성해야됩니다.
장점
- 테이블 정규화
- 외래 키 참조 무결성 제약조건으로 활용 가능
- 저장공간의 효율화
단점
- 조회시 조인을 많이 사용하여 성능 저하
- 조회 쿼리가 복잡함
- 데이터 저장시 INSERT SQL 2번 호출함
아래와 같이 조인 전략으로 사용하려면
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Item {
@Id @GeneratedValue
private Long id;
private String name;
private int price;
}
2번째 라인을 추가해줘야합니다. 설정을 JOINED로 해주게 되면 아래와 같은 결과를 얻을 수 있습니다.
Hibernate:
create table album (
artist varchar(255),
id bigint not null,
primary key (id)
)
Hibernate:
create table book (
author varchar(255),
isbn varchar(255),
id bigint not null,
primary key (id)
)
Hibernate:
create table item (
id bigint not null,
name varchar(255),
price integer not null,
primary key (id)
)
Hibernate:
create table movie (
actor varchar(255),
director varchar(255),
id bigint not null,
primary key (id)
)
각 테이블의 ID는 Item 테이블의 ID라는 것을 알 수 있습니다.
단일 테이블 전략
장점
- 조인이 필요 없으므로 일반적으로 조회 성능이 빠름
- 조회 쿼리가 단순함
단점
- 자식 엔티티가 매핑한 컬럼은 모두 Null 허용
: null 허용 한다는것이 만약 ALBUM 관련 데이터를 넣어줄 때에는 MOVIE 나 BOOK 관련 데이터는 모두 Null을 허용해줘야합니다.
- 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있으며, 상황에 따라서 조회 성능이 느려질 수 있음
단일테이블을 보면 DTYPE 컬럼이 생긴것을 알 수 있습니다.
DTYPE은 어떤 테이블에 관련되서 ITEM이 사용하고 있는지에 대해 구체적으로 알 수 있게 해줍니다.
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
@Getter @Setter
public class Item {
@Id @GeneratedValue
private Long id;
private String name;
private int price;
}
DTYPE을 추가하고 싶을 때에는 3번째 라인을 추가하면됩니다.
DTYPE의 이름을 따로 지정하고 싶을 때에는 "" 안에 원하는 이름을 지정하게 되면 그에 따라 생성되게 됩니다.
DTYPE은 조인 전략에서는 어떻게든 관련 테이블을 찾을 수 있지만, 단일테이블 전략에서는 찾기 힘들어 꼭 들어가야 됩니다.
@DiscriminatorColumn(name = "")
그리고 DTYPE 안에 각 테이블관련된 이름을 지정하고 싶으면
@Entity
@DiscriminatorValue("Book")
public class Book extends Item{
private String author;
private String isbn;
}
2번째 라인처럼 이름을 따로 지정해주면 됩니다.
맨 위와다르게 @Inheritance를 사용하면서 단일 테이블로 수정하고 싶을 때에는 JOINED -> SINGLE_TABLE로 변경하면 됩니다.
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
구현 클래스마다 테이블 전략
이 전략은 데이터베이스 설계자와 ORM 전문가 둘다 추천하지 않습니다.
장점
- 서브 타입을 명확하게 구분해서 처리할 때 효과적
- not null 제약 조건 사용 가능
단점
- 여러 자식 테이블을 함께 조회할 때 성능이 느림
: Item ID로 조회할 경우 어느 테이블에 들어가있는지 알 수 없으므로 union을 사용해서 ALBUM, MOVIE, BOOK을 모두 조회하게 됩니다.
- 자식 테이블을 통합해서 쿼리하기 어려움
아래와 같이 구현 클래스마다 테이블 전략으로 실행하고 싶으면 TABLE_PER_CLASS 로 변경하면 됩니다.
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
⭐️ 참고한 곳
자바 ORM 표준 JPA 프로그래밍 - 기본편
https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
배워야 할 것이 더 많은 주니어 개발자입니다. 🐣
내용 전달보다는 정리를 목적으로 포스팅을 하고 있습니다.
잘못 된 내용이나 부족한 부분은 댓글로 주시면 감사드리겠습니다.