[JPA 기본편] 6. 상속관계 매핑

HJ·2024년 2월 23일
0

JPA 기본편

목록 보기
6/10
post-thumbnail

김영한 님의 자바 ORM 표준 JPA 프로그래밍 - 기본편 강의를 보고 작성한 내용입니다.


1. 상속관계 매핑

관계형 데이터베이스에 상속관계는 없습니다. 슈퍼타입 서브타입 관계라는 모델링 기법이 객체의 상속과 유사합니다. 그래서 상속관계를 매핑할 때는 객체의 상속 구조와 DB의 슈퍼타입, 서브타입 관계를 매핑합니다.

위와 같은 구조가 있다고 가정했을 떄, 슈퍼타입 서브타입 논리 모델을 실제 물리 모델로 구현하는 3가지 방법이 존재합니다. 객체 입장에서는 상속관계를 지원하기 때문에 하나의 구조가 나오게 되므로 어떤 전략을 사용해도 JPA 에서 매핑이 가능합니다.


1-1. 조인전략

조인전략은 각각 테이블로 변환하는 방식인데 모든 테이블을 생성해 데이터를 나누고 조인으로 구성하는 것입니다. 필요 시 데이터를 같이 가져올 때 조인으로 가져오면 됩니다.

예를 들어 Album 이 저장될 때 Item 테이블과 Album 테이블에 데이터를 나누어 저장하는 방식입니다. 데이터를 가져올 때는 ITEM_ID 를 통해 가져옵니다.

Item 테이블만 보았을 때 Album 인지, Movie 인지, Book 인지 구분이 안되기 때문에 Item 테이블 안에 이들을 구별하는 컬럼을 생성합니다.


[ 구현 코드 ]

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
public class Item {
    @Id @GeneratedValue
    private Long id;
    private String name;
    private int price;
}
// -----------------------
@Entity
public class Movie extends Item {
    @Id @GeneratedValue
    private Long id;
    private String actor;
    private String director;
}

@Inheritance(strategy = InheritanceType.JOINED) 라고 선언하면 위의 그림처럼 설계한 것과 동일하게 테이블이 생성됩니다.

@DiscriminatorColumn 은 상위 클래스에 지정하고, name 속성을 지정하지 않으면 DTYPE 이라는 이름의 컬럼이 생성됩니다.

@DiscriminatorValue 는 하위 클래스에 지정하고 저장하고 싶은 데이터를 지정할 수 있는데, 어노테이션 자체를 선언하지 않거나 지정하지 않으면 엔티티명이 들어갑니다.


[ 실행 코드 ]

Movie movie = new Movie();
movie.setActor("actorA");
movie.setDirector("directorA");
movie.setName("nameA");
movie.setPrice(10000);

em.persist(movie);
// -----------------------
Movie findMovie = em.find(Movie.class, movie.getId());

위위 코드를 실행하면 Item 테이블과 Movie 테이블에 각각 Insert 쿼리가 나가게 되고
Item 테이블에 name, price 가, Movie 테이블에 actor, director,ITEM_ID 가 저장됩니다.

저장된 것을 보면 ITEM 의 ID 와 MOVIE 의 ID 가 동일한 것을 확인할 수 있으며, ITEM 에 DTYPE 이라는 컬럼이 생성되고, Movie 라는 값이 들어있는 것을 확인할 수 있습니다.
만약 Movie 에 @DiscriminatorValue("M") 을 선언하면 Movie 가 아닌 M 이라는 값으로 들어가게 됩니다.

-- 조회 SQL 로그
Hibernate: 
    select
        m1_0.id,
        m1_1.name,
        m1_1.price,
        m1_0.actor,
        m1_0.director 
    from
        Movie m1_0 
    join
        Item m1_1 
            on m1_0.id=m1_1.id 
    where
        m1_0.id=?

em.find() 를 할 때는 Movie 와 Item 을 inner 조인해서 데이터를 가져오게 됩니다.


1-2. 단일 테이블 전략

단일 테이블 전략이란 논리 모델을 하나의 테이블로 다 합치는 방법입니다. 조인전략과 마찬가지로 Album 인지, Movie 인지, Book 인지 구분하기 위해 이들을 구별하는 컬럼을 생성합니다.

조인전략에서 어노테이션을 @Inheritance(strategy = InheritanceType.SINGLE_TABLE) 로 변경하면 됩니다. 아래 테이블 생성 쿼리를 보면 Movie 에 있는 컬럼들도 Item 에 생성되는 것을 확인할 수 있습니다.

조인전략에서는 @DiscriminatorColumn 가 필수로 필요했지만, 단일 테이블 전략에서는 생략해도 자동으로 생성됩니다.

Hibernate: 
create table Item (
    price integer not null,
    id bigint not null,
    DTYPE varchar(31) not null,
    actor varchar(255),
    director varchar(255),
    name varchar(255),
    primary key (id)
)

단일 테이블 전략은 Insert 도 한 번에 되고, select 도 조인 필요없이 한 테이블에서만 조회하면 되기 때문에 성능상 훨씬 좋습니다.


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

쓰면 안되는 전략이라고 하셨습니다

테이블을 생성할 때 각각의 테이블을 생성하고, 모든 테이블이 Item 에 해당하는 name, price 와 같은 컬럼들을 가지게 하는 전략입니다.

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) 로 선언하면 됩니다.
이때 Item 은 추상 클래스로 선언해야 하고, 코드 실행 결과, movie 테이블에 name 과 price 컬럼이 들어있는 것을 확인할 수 있습니다.

조회할 때 문제가 발생하는데 em.find(Item.class, movie.getId()) 를 실행하면 모든 자식들을 UNION 을 걸어서 전부 확인하게 됩니다.




2. 매핑정보 상속

@MappedSuperclass 라는 어노테이션이 있는데 공통 매핑 정보가 필요할 때 사용합니다.

예를 들어, 객체 입장에서 Member 와 Seller 에 id 와 name 이라는 필드가 공통적으로 있을 때 이를 부모를 만들어 속성만 상속해서 사용하고 싶을 때 사용합니다. 속성만 상속하고 DB 테이블은 따로 사용하게 됩니다.


@MappedSuperclass
public class BaseEntity {
    private String createdBy;
    private String createdData;
    ...
}
// --------------------------------------
public class Member extends BaseEntity {
    ...
}

@MappedSuperclass 가 선언된 클래스가 매핑 정보만 받는 슈퍼 클래스라고 생각하면 됩니다. BaseEntity 는 테이블이 생성되지 않고 Member 테이블이 생성될 때 속성들이 추가되어 생성됩니다.

상속 받는 자식 클래스에 매핑 정보만 제공할 뿐 엔티티가 아니기 때문에 테이블과 매핑되지 않고, 조회나 검색도 불가능합니다. 이를 구현할 때는 추상 클래스를 권장합니다.

참고로 JPA 에서 extends 를 사용할 때는 @Entity@MappedSuperclass 가 붙은 클래스들만 상속 받을 수 있습니다.

profile
공부한 내용을 정리해서 기록하고 다시 보기 위한 공간

0개의 댓글