Spring/자바 ORM 표준 JPA 프로그래밍

[JPA 기본편] 7. 고급 매핑

kyung.Kh 2024. 5. 20. 03:36

상속 관계 매핑

: 객체의 상속과 구조와 DB의 슈퍼-서브 타입 관계를 매핑해준다.

(관계형 DB는 상속 관계X, 슈퍼-서브 타입 관계라는 모델링 기법이 객체 상속과 유사)

  1. JOINED : 조인 전략
  2. SINGLE_TABLE : 단일 테이블 전략
  3. TABLE_PER_CLASS : 구현 클래스마다 테이블 전략

1. 조인 전략

: 가장 정규화된 모델링으로 엔티티들을 각각 테이블로 만들어서 부모의 기본 키를 기본 키 + 외래 키로 사용한다.(슈퍼 타입에 조인)

슈퍼 타입 엔티티(부모 엔티티)에 @Inheritance(strategy = InheritanceType.JOINED)를 추가한다.

 

다만, 객체는 타입이 있는데 테이블은 타입에 개념이 없다. 따라서 슈퍼 타입의 속성으로 DTYPE을 두어서, 슈퍼 타입 테이블을 보고 어떤 서브 타입인지 구분한다. 조인 전략에서는 선택이지만 아래의 단일 테이블 전략에서는 필수이다.

  • 슈퍼 타입 : @DiscriminatorColumn을 추가하면 DTYPE이 추가된다. 기본값은 서브 타입들의 컬럼 명(엔티티 명)이다.
  • 서브 타입 : @DiscriminatorValue("???")를 추가하면 DTYPE의 각각의 서브 타입들의 이름을 설정할 수 있다.

  • 장점 : 테이블 정규화. 외래 키 참조 무결성 제약조건 활용 가능. 저장 공간 효율화
  • 단점 : 조회 시 조인을 많이 사용, 성능 저하. 조회 쿼리가 복잡함. 데이터 저장 시 INSERT SQL 2번 호출
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn  // DTYPE이 생김 : Album에서 들어온건지, Movie에서 들어온건지, Book에서 들어온건지 나타남
@Getter @Setter
public class Item {

    @Id @GeneratedValue
    private Long id;

    private String name;
    private int price;
}

@Entity
@Getter @Setter
@DiscriminatorValue("B")  // 만약 Item의 @DiscriminatorColumn에서 나타나는 값을 정하고 싶을 때 추가(기본 값은 Column명)
public class Album extends Item {

    private String artist;
}

@Entity
@Getter @Setter
@DiscriminatorValue("C")  // 만약 Item의 @DiscriminatorColumn에서 나타나는 값을 정하고 싶을 때 추가(기본 값은 Column명)
public class Movie extends Item {

    private String director;
    private String actor;
}

@Entity
@Getter @Setter
@DiscriminatorValue("A")  // 만약 Item의 @DiscriminatorColumn에서 나타나는 값을 정하고 싶을 때 추가(기본 값은 Column명)
public class Book extends Item {

    private String author;
    private String isbn;
}

2. 단일 테이블

: 모든 속성들으 하나에 테이블에 몰아넣는 전략으로 단순한 설계일 때 사용한다.

슈퍼 타입 엔티티(부모 엔티티)에 @Inheritance(strategy = InheritanceType.SINGLE_TABLE)를 추가한다.

 

여기서는 테이블 하나에 모든 속성이 들어가기 때문에 구분을 위해 DTYPE이 필수로 들어가야 한다. 따라서 @Inheritance(strategy = InheritanceType.SINGLE_TABLE)를 추가하면 @DiscriminatorValue를 추가하지 않아도 자동으로 DTYPE이 적용된다.

  • 장점 : 조인이 필요 없으므로 일반적으로 조회 성능이 빠르고 조회 쿼리가 단순함
  • 단점 : 자식 엔티티가 매핑한 컬럼은 모두 null 허용한다. 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있으며 상황에 따라서 조회 성능이 오히려 느려질 수 있다.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn  // 단일 테이블 전략은 한 테이블에 모두 들어가기 때문에 이게 없어도 자동으로 DTYPE이 추간된다.
@Getter @Setter
public class Item {

    @Id @GeneratedValue
    private Long id;

    private String name;
    private int price;
}

// 나머지는 조인 전략의 자식 테이블과 동일
// 쿼리가 한 번에 나가는 것을 확인할 수 있음
Hibernate: 
    create table Item (
        price integer not null,
        id bigint not null,
        DTYPE varchar(31) not null,
        actor varchar(255),
        artist varchar(255),
        author varchar(255),
        director varchar(255),
        isbn varchar(255),
        name varchar(255),
        primary key (id)
    )

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

: 서브 타입들을 단일 테이블로 만든다. 사용하면 안됨

  • 장점 : 서브 타입을 명확하게 구분해서 처리할 때 효과적이며, not null 제약조건 사용 가능
  • 단점 : 여러 자식 테이블을 함께 조회할 때 성능이 느림(UNION SQL 필요). 자식 테이블을 통합해서 쿼리하기 어려움

코드 실행 시, Item을 생성하는 쿼리가 없음.

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@DiscriminatorColumn  // 의미 없음
public abstract class Item {  // 직접 생성해서 사용할 일이 없으므로 추상 클래스로 만든다.

    @Id @GeneratedValue
    private Long id;

    private String name;
    private int price;
}

// 나머지는 동일

@MappedSuperclass

: 객체를 만들 때, 반복되는 속성들을 하나로 모아서 저장해두고 필요할 때마다 사용하기 위한 어노테이션으로 부모 클래스를 상속 받는 자식 클래스에 매핑 정보만 제공한다. (BaseEntity 만들 때 사용함)

 

상속 관계 매핑과는 다르고, 자식 클래스에 매핑 정보만 제공할 뿐 엔티티가 아니기 때문에 테이블과 매핑되지 않는다.(BaseEntity가 create로 생성되지 않고, 상속받은 클래스에 컬럼으로 추가된다) 조회와 검색이 불가능하다.(em.find(BaseEntity) 불가) 직접 생성해서 사용할 일이 없으므로 추상 클래스로 만드는 것을 권장한다.

 

주로 등록일, 수정일, 등록자, 수정자 같은 전체 엔티티에서 공통으로 적용하는 정보를 모을 때 사용한다.

참고: @Entity 클래스는 엔티티나 @MappedSuperclass로 지정한 클래스만 상속이 가능하다.

@MappedSuperclass  // 매핑 정보만 받는 super class
@Getter @Setter
public class BaseEntity {

    @Column(name = "INSERT_MEMBER")  // 컬럼명을 지정할 수도 있음
    private String createBy;
    private LocalDateTime createdDate;
    private String lastModifiedBy;
    private LocalDateTime lastModifiedDate;
}

@Entity
@Getter @Setter
public class Team extends BaseEntity {  // BaseEntity를 상속받아서 사용함

    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;
	
    ...
}

[출처]

https://www.inflearn.com/course/ORM-JPA-Basic -> 이 글은 김영한님의 JPA 강의 중 7을 듣고 정리한 내용입니다.

728x90