- Today
- Total
개발하는 고라니
[JPA] 상속관계 매핑 본문
상속관계 매핑
RDB에는 객체에서의 상속관계와 같은 상속관계가 없다. DB가 지원하는 상속이 있으나, 객체의 상속과는 거리가 멀다. 바로 슈퍼타입 / 서브타입 관계라는 모델링 기법이 존재하긴 한다.
상속관계 매핑이란 객체의 상속구조와 DB의 슈퍼타입 / 서브타입 관계를 매핑하는 것을 말한다.
다음과 같이 객체의 상속관계가 있다고 할 때, 이를 JPA를 이용해 매핑해보고, 그 때의 테이블을 보도록 한다.
Entity
- Product
@Entity
//@Inheritance(strategy = InheritanceType.JOINED)
//@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
//@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@DiscriminatorColumn(name = "DTYPE")
@Getter
@Setter
public abstract class Product {
@Id @GeneratedValue
@Column(name = "PRODUCT_ID")
private Long id;
private String name;
private int price;
}
/*
JOINED : 조인 전략
SINGLE_TABLE : 싱글 테이블 전략
TABLE_PER_CLASS : 구현 클래스마다 테이블 전략
@DiscriminatorColumn(name = "DTYPE")
- 부모 클래스에 써주는 어노테이션, name을 지정하지 않으면 default가 "DTYPE"
- Snack / Food / Beverage 중 어떤 객체인지 구분하기 위한 컬럼
*/
- Food
@Entity
@DiscriminatorValue("Food")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Food extends Product{
private String ingredient;
}
- Snack
@Entity
@DiscriminatorValue("Snack")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Snack extends Product{
private int capacity;
private String taste;
}
- Beverage
@Entity
@DiscriminatorValue("Beverage")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Beverage extends Product{
private int capacity;
private int container;
}
조인 전략 - JOINED
조인 전략은 테이블을 나누고, 각 테이블에 데이터를 저장한다. 이후 데이터를 가져올 때 JOIN으로 가져온다.
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "DTYPE")
@Getter
@Setter
public abstract class Product {
@Id @GeneratedValue
@Column(name = "PRODUCT_ID")
private Long id;
private String name;
private int price;
}
@Inheritance의 속성 값을 위와 같이 놓았을 때 테이블이 어떻게 생겨나고, 객체 저장 시 어떻게 테이블에 저장 쿼리가 나가는지 살펴보자
create table Beverage (
capacity integer not null,
container integer not null,
PRODUCT_ID bigint not null,
primary key (PRODUCT_ID)
)
12:25:25.716 [main] DEBUG org.hibernate.SQL -
create table Food (
ingredient varchar(255),
PRODUCT_ID bigint not null,
primary key (PRODUCT_ID)
)
12:25:25.717 [main] DEBUG org.hibernate.SQL -
create table Product (
DTYPE varchar(31) not null,
PRODUCT_ID bigint not null,
name varchar(255),
price integer not null,
primary key (PRODUCT_ID)
)
12:25:25.718 [main] DEBUG org.hibernate.SQL -
create table Snack (
capacity integer not null,
taste varchar(255),
PRODUCT_ID bigint not null,
primary key (PRODUCT_ID)
)
조인 전략으로 설정 후 실행시키면 위와 같이 Product, Snack, Food, Beverage 테이블이 생성됨을 알 수 있다.
그럼 엔티티를 저장할 때의 쿼리는 어떻게 될까?
Snack snack = new Snack();
snack.setName("popcorn");
snack.setCapacity(300);
snack.setTaste("카라멜");
snack.setPrice(2000);
em.persist(snack);
insert
into
Product
(name, price, DTYPE, PRODUCT_ID)
values
(?, ?, 'Snack', ?)
12:25:25.814 [main] DEBUG org.hibernate.SQL -
insert
into
Snack
(capacity, taste, PRODUCT_ID)
values
(?, ?, ?)
총 2번의 INSERT 문이 나감을 알 수 있다.
단일 테이블 전략 - SINGLE_TABLE
데이터베이스의 논리모델을 한 테이블로 합친 것을 말한다.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
@Getter
@Setter
public abstract class Product {
@Id @GeneratedValue
@Column(name = "PRODUCT_ID")
private Long id;
private String name;
private int price;
}
이 때의 테이블 생성 쿼리를 보면 다음과 같다.
create table Product (
DTYPE varchar(31) not null,
PRODUCT_ID bigint not null,
name varchar(255),
price integer not null,
ingredient varchar(255),
capacity integer,
taste varchar(255),
container integer,
primary key (PRODUCT_ID)
)
Product테이블 하나만 생성되고, 모든 컬럼들이 한 곳에 뭉쳐있게 된다. 그리고 단일 테이블 전략에서 DTYPE은 필수이다.
저장할 때 쿼리 => 1번만 실행
insert
into
Product
(name, price, capacity, taste, DTYPE, PRODUCT_ID)
values
(?, ?, ?, ?, 'Snack', ?)
구현 클래스마다 테이블 전략 - TABLE_PER_CLASS
공통된 부모 테이블 없이 3개의 각 테이블을 만들어 놓는다.
이 전략은 DBA와 ORM 전문가 둘 다 추천하지 않는 방법이라고 한다... 그러니 쓰지말도록 하자
각 전략의 장단점
- 조인 전략
- 장점
- 테이블이 정규화 되어있다.
- 외래키 참조 무결성 제약조건 활용이 가능하다. (Product의 ID로 Food, Snack, Beverage의 PK 및 FK가 됨)
- 데이터가 Fit하게 들어가므로 저장공간이 효율적이다.
- 단점
- 조회 시, 조인을 많이 사용하므로 성능 저하 우려가 있다
- 조회 쿼리가 타 전략에 비해 복잡하다.
- INSERT 쿼리가 2번 나간다.
- 단일 테이블 전략
- 장점
- 조인이 필요 없어 비교적 조회성능이 빠르다.
- 조회 쿼리가 단순하다.
- 단점
- 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 한다. 어떤 데이터가 저장될 지 모르므로
- 단일 테이블에 모든 데이터를 저장하므로 테이블이 커질 수 있다.
- 상황에 따라 조회 성능이 오히려 느려질 우려가 있다.
- 구현 클래스마다 테이블 전략은 생략 .. :)
강사님의 결론 : 조인 전략을 기본으로 하되, 정말 간단한 테이블 구조일 경우 단일 테이블 전략으로 사용한다. 하지만 왠만해서 상속관계 매핑은 잘 하지 않도록 한다.
@MappedSuperclass
#References
'Framework > JPA (Hibernate)' 카테고리의 다른 글
[JPA] 벌크 연산 (Bulk Operation) (0) | 2021.08.05 |
---|---|
[JPA] 페치 조인 (FETCH JOIN) (0) | 2021.08.05 |
[JPA] Cascade / OrphanRemoval (0) | 2021.07.25 |
[JPA] Proxy (0) | 2021.07.23 |
[JPA] JPA란? (0) | 2021.07.15 |