[JPA] 5.상속관계 매핑

Updated:

상속관계 매핑

객체는 상속관계를 이용하여 모델링하면 되지만 DB는 상속관계가 존재하지 않는다는 차이점이 존재한다. 여기서 DB는 어떻게 상속관계를 처리할 것인지에 대한 고민이 필요하다. 이 게시글에서는 여러가지 상속관계 매팽 기법들과 사용하는 어노테이션들에 대해 알아본다

배경지식

RDB는 상속관계가 존재하지 않으므로 그나마 유사한 슈퍼타입, 서브타입 관계라는 모델링 기법과 객체의 상속을 매핑한다

jpa1

아래부터는 이 예시를 이용하여 설명을 하고자 한다

상속관계 매핑 종류

  1. 조인 전략: 각각 테이블로 변환
  2. 단일 테이블 전략: 통합 테이블로 변환
  3. 구현 클래스마다 테이블 전략: 서브타입 테이블로 변환

주요 어노테이션

  • @Inheritance
    • 상속관계 매핑을 수행한다고 명시한다
    • strategy속성을 통해 조인 전략, 단일 테이블 전략, 구현클래스마다 테이블 전략 3가지로 구분한다
  • @DiscriminatorColumn
    • 어떤 컬럼을 테이블 구분값으로 사용할지 명시한다
  • @DiscriminatorValue
    • 각 테이블을 구분하기 위해 테이블별로 @DiscriminatorColumn로 어떤 값을 설정할지 명시한다

1. 상속관계 매핑의 전략들

1-1. 조인 전략

주로 가장 많이 사용하는 전략이다

jpa2

  • 객체의 상속관계와 유사한 모양으로 테이블을 모델링한다
  • 슈퍼타입(ITEM)의 DTYPE 컬럼으로 서브타입(ALBUM, MOVIE, BOOK)을 구분한다
    • 사실 조인 전략은 각각의 서브타입들의 테이블이 분리되어 존재하기 때문에 DTYPE이 없어도 된다
  • 각각의 서브타입은 슈퍼타입의 PK를 자신의 PK 및 FK로 가진다
  • 테이블 정규화 측면에서 유용한 전략이지만 조회 시에 조인을 많이 사용하고, 저장시에는 INSERT문을 슈퍼타입, 서브타입 총 2개를 호출해야 한다는 단점이 존재한다

이제 코드를 통해 살펴보자

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "dtype")
@Getter @Setter
public abstract class Item {

    @Id @GeneratedValue
    private Long id;

    private String name;
    private int price;
}
@Entity
@DiscriminatorValue(value = "A")
@Getter @Setter
public class Album extends Item{
    private String artist;
}
@Entity
@DiscriminatorValue(value = "B")
@Getter @Setter
public class Book extends Item{
    private String author;
    private String isbn;
}
@Entity
@DiscriminatorValue(value = "M")
@Getter @Setter
public class Movie extends Item{
    private String director;
    private String actor;
}
  • @Inheritancestrategy 속성을 통해 조인 전략을 사용함을 명시한다
  • @DiscriminatorColumn를 통해 서브타입 테이블의 구분을 위해 dtype을 사용함을 명시한다
  • @DiscriminatorValue를 통해 각각의 서브타입 테이블이 value 속성에 명시한 값을 dtype으로 가짐을 명시한다

1-2. 단일 테이블 전략

프로젝트가 복잡하지 않고 변경될 여지가 거의 없는 경우에 사용하는 전략이다

jpa3

  • 몇개의 서브타입이 존재하든 하나의 테이블만을 만들고 dtype 컬럼을 통해 구분한다
  • 조인이 필요없어 조회 성능이 우수하고 조회 쿼리 또한 단순하다
  • 다른 서브타입들의 컬럼 모두가 들어있기 때문에 null을 허용하고 테이블이 거대해지는 문제가 발생한다
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype")
@Getter @Setter
public abstract class Item {

    @Id @GeneratedValue
    private Long id;

    private String name;
    private int price;
}
  • 조인 전략의 코드에서 @Inheritancestrategy 속성만 InheritanceType.SINGLE_TABLE로 변경해주면 된다
  • DB를 살펴보면 조인 전략과 다르게 ALBUM, BOOK, MOVIE 테이블이 생성되지 않고 ITEM 테이블에 모든 컬럼들이 들어있음을 확인할 수 있다
    • 단, 다른 서브타입의 컬럼들은 null값으로 들어간다

1-3. 구현 클래스마다 테이블 전략

DB 설계자와 ORM 전문가 모두 추천하지 않는 전략으로 사용하지 말 것!!!

jpa4

  • 조인 전략과 비슷하지만 슈퍼타입(ITEM)을 없애고 서브타입(ALBUM, MOVIE, BOOK)에 슈퍼타입의 컬럼들을 모두 집어넣은 방식이다
    • 실제로 DB에 슈퍼타입(ITEM)이 생성되지 않고 서브타입(ALBUM, MOVIE, BOOK)만 생성된다
  • 데이터 저장 시에는 편리하지만 조회 시에 UNION SQL이 필요하여 성능이 느리다는 단점을 가진다
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Getter @Setter
public abstract class Item {

    @Id @GeneratedValue
    private Long id;

    private String name;
    private int price;
}
  • 서브타입에서 각각의 테이블이 존재하기 때문에 @DiscriminatorColumn가 필요없다

2. @MappedSuperclass

상속관계 매핑과 관계없이 여러 객체가 동일한 속성을 사용할때 중복코드를 제거하기 위해 사용하는 어노테이션이다

jpa5

  • 객체들 간의 공통 속성은 슈퍼타입으로 따로 만들어 생성하고 서브타입은 각자만의 속성을 가지고 슈퍼타입을 상속받도록 설계한다
  • 상속관계 매핑이 아니며 엔티티도 아님을 명심하고 직접 생성하여 사용할 일이 없으므로 추상 클래스가 권장된다
  • 단지, 여러 엔티티가 공통으로 사용하는 필드를 모으는 역할을 한다
  • 주로 등록일, 수정일, 등록자, 수정자 같이 전체 엔티티에서 공통으로 사용되는 코드의 중복성을 제거하기 위해 사용한다
@MappedSuperclass
@Getter @Setter
public abstract class BaseEntity {
    private String createdBy;
    private String updatedBy;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
}
  • 위와 같이 공통 속성을 모아놓고 @MappedSuperclass를 붙여 추상클래스로 만든다
  • 위 필드가 필요한 엔티티는 extends 키워드로 상속받아 사용한다

Tags: ,

Categories:

Updated:

Leave a comment