반응형
05-14 05:47
Today
Total
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
관리 메뉴

개발하는 고라니

[JPA] 상속관계 매핑 본문

Framework/JPA (Hibernate)

[JPA] 상속관계 매핑

조용한고라니 2021. 7. 23. 13:23
반응형

상속관계 매핑

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

 

[Spring Boot] @MappedSuperClass

데이터베이스의 테이블은 종종 '시간'의 데이터를 갖는 컬럼이 존재하는 경우가 있다. 예를 들어 Board 테이블의 Reg Date라던지 Mod Date 등등.. JPA를 이용한다고 해서 예외는 아니다. MyBatis를 이용했

dev-gorany.tistory.com

 

#References

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 본 강의는 자바 백엔

www.inflearn.com

 

반응형

'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
Comments