상속관계 매핑

 

객체에는 상속관계가 존재하지만, DB에는 상속이 존재하지 않는다.

하지만 DB에는 슈퍼- 서브타입 이라는 모델링 기법이 존재하는데, 이것이 객체의 상속관계와 유사하다.

 

상속관계 매핑이란, 객체의 상속, 구조와 DB의 슈퍼타입, 서브타입을 매핑하는것이다.

 

슈퍼-서브타입 모델링으로 공통된 속성들을 하나로 묶거나, 하나로 합칠 수 있다.

이렇게 슈퍼타입, 서브타입 논리 모델을 실제 물리 모델로 구현하는 방법들이 JPA에서 세 가지가 존재한다.

 

1. 데이터를 정규화시켜서 각각의 테이블로 변환하는 조인 전략

 

비즈니스적으로 중요하고 복잡할 때 쓴다.

기본적으로 세 전략 중 이 전략을 많이 사용한다.

슈퍼타입으로 사용할 상위 엔티티 생성 후 @Inheritance(strategy = InheritanceType.JOINED) 를 클레스에 추가한다.

또한, @DiscriminatorColumn 라는 어노테이션을 추가하여 테이블상에 DTYPE 이라는것을 추가하는데,

조인된 서브타입 테이블의 이름을 저장해 데이터를 구분하는데 도움을 준다.

(name속성을 변경하여 컬럼명을 DTYPE 외에도 다양하게 사용할 수 있다. )

상속받은 엔티티 클레스에 @DiscriminatorValue(value = "M") 를 추가해주면,(@DiscriminatorValue("M") 도 가능)

엔티티 명 대신 설정한 값인 M이 들어간다.

 

이후 아래처럼 하위 엔티티에서 상위 엔티티를 상속받으면 된다.

결론적으로 아래 코드에서 변경점 하나 없이 @ Inheritance의 stategy 속성 변경만으로 테이블 구조가 달라진다.

package com.example.jpa.domain;  
  
import jakarta.persistence.*;  
import lombok.Getter;  
import lombok.Setter;  
  
import java.util.ArrayList;  
import java.util.List;  
  
@Entity  
@Getter  
@Setter  
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
public class Item {  
  
@Id @GeneratedValue  
@Column(name = "item_id")  
private Long id;  
  
private String name;  
private int price;  
private int stockQuantity;  
@ManyToMany(mappedBy = "items")  
private List<Category> categories = new ArrayList<>();  
}

package com.example.jpa.domain;  
  
import jakarta.persistence.Entity;  
  
@Entity  
public class Book extends Item{  
private String author;  
private String isbn;  
}

package com.example.jpa.domain;  
  
import jakarta.persistence.Entity;  
@Entity  
public class Album extends Item{  
private String artist;  
}

package com.example.jpa.domain;  
  
import jakarta.persistence.Entity;  
  
@Entity  
public class Movie extends Item{  
private String director;  
private String actor;  
}

상위 테이블과 하위 테이블 모두를 접근해야하므로 쿼리가 2번 나간다는 특징이 있다.

 

  • 장점
    • 테이블 정규화
    • 외래 키 참조 무결성 제약조건 활용 가능
    • 저장공간 효율화
  • 단점
    • 조회 시 조인을 많이 사용, 성능 저하
    • 조회 쿼리가 복잡함
    • 데이터 저장 시 INSERT SQL 2번 호출

가장 정석적인 전략이다.

 

2. 여러 테이블을 하나라 통합하는 단일 테이블 전략

 

논리 모델을 한 테이블로 구성하는 전략이다.

데이터도 얼마 안되고 너무 단순하고 확장할 일도 없을때 쓰면 좋다고 한다.

 

위에 상술한 @Inheritance 속성을 InheritanceType.SINGLE_TYPE으로 변경하면 된다.

Hibernate: 
    create table item (
        price integer not null,
        stock_quantity integer not null,
        item_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 (item_id)
    ) engine=InnoDB

위 코드들을 보면, 각자의 세부 엔티티들은 상위 엔티티인 item을 확장했고, 

그 item 테이블 쿼리를 보면 테이블에 세부 엔티티가 전부 들어간것을 볼수 있다.

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

3. 슈퍼타입으로 모으지 않고 서브타입에 각각 속성들을 포함시키는 구현 클래스마다 전략

 

@Inheritance 속성을 InheritanceType.TABLE_PER_CLASS 로 변경하면 된다.

상위 테이블을 없애고 각각의 하부 테이블에 상위테이블 값들을 넣는 방식이다.

참고로, 이 전략에서는 @DiscriminatorColumn 이 적용되지 않는다.

상위 테이블인 ITEM이 사라졌기 때문에, 구분할 필요가 없기 때문이다.

 

이 전략은 단순하게 값을 넣고 뺄 때는 딱 좋은데,  상위타입으로 엔티티를 find하게되면 문제가 생긴다.

타입을 구분할 수 없기 때문이다.

Item findItem = em.find(Item.class, movie.getId());  
log.info("findItem={}",findItem);

 

조회시 쿼리가 너무 복잡해진다.

Hibernate: 
    select
        i1_0.id,
        i1_0.clazz_,
        i1_0.name,
        i1_0.price,
        i1_0.stock_quantity,
        i1_0.artist,
        i1_0.author,
        i1_0.isbn,
        i1_0.actor,
        i1_0.director 
    from
        (select
            price,
            stock_quantity,
            id,
            artist,
            name,
            null as author,
            null as isbn,
            null as actor,
            null as director,
            1 as clazz_ 
        from
            album 
        union
        all select
            price,
            stock_quantity,
            id,
            null as artist,
            name,
            author,
            isbn,
            null as actor,
            null as director,
            2 as clazz_ 
        from
            book 
        union
        all select
            price,
            stock_quantity,
            id,
            null as artist,
            name,
            null as author,
            null as isbn,
            actor,
            director,
            3 as clazz_ 
        from
            movie
    ) i1_0 
where
    i1_0.id=?

 

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

@MappedSuperclass

테이블과는 관계 없고, 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모으는 역할을 한다.

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

 

package com.example.jpa.domain;  
  
import jakarta.persistence.MappedSuperclass;  
import lombok.Getter;  
import lombok.Setter;  
  
import java.time.LocalDateTime;  
@Getter  
@Setter  
@MappedSuperclass  
public abstract class BaseEntity {  
private String createdBy;  
private LocalDateTime createdDate;  
private String lastModifiedBy;  
private LocalDateTime lastModifiedDate;  
}
------------------------------------
public class Team extends BaseEntity {...}

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

 

  • 특징 
    • 상속관계 매핑이 아님
    • 엔티티 X, 테이블과 매핑 X
    • 부모 클래스를 상속받는 자식 클래스에 매핑 정보만 제공한다.
    • 조회, 검색 불가(em.find(BaseEntity) 불가)
    • 직접 생성해서 사용할 일이 없으므로 추상 클래스 권장

'Java > JPA' 카테고리의 다른 글

JPA - 즉시로딩과 지연로딩, CASCADE와 고아객체  (1) 2023.10.10
JPA - 프록시  (0) 2023.10.10
JPA - 연관관계  (0) 2023.10.01
JPA - 기본 키 매핑 : @GeneratedValue  (0) 2023.09.23
JPA - 플러시, 준영속 상태  (0) 2023.09.20

+ Recent posts