[JPA]상속관계 매핑
프로그래밍 언어/JPA

[JPA]상속관계 매핑

반응형

상속관계 매핑

객체는 상속관계가 존재하지만, 관계형 데이터베이스는 상속 관계가 없다.

하지만! 슈퍼타입 서브타입 관계라는 모델링 기법이 있으며, 이 기법은 객체 상속과 유사하다.

 

상속관계 매핑?

객체의 상속과 구조와 DB의 슈퍼타입 서브타입 관계를 매핑

 

슈퍼타입, 서브타입 논리 모델을 실제 물리 모델로 구현하는 방법은 3가지 전략이 있다.

1. 조인 전략

2. 단일 테이블 전략

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

 

오늘 예시로 사용할 (좌) 논리모델, (우) 물리모델 

 

엔티티

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn
public class Item {

    @Id @GeneratedValue
    private Long id;
    private String name;
    private int price;
}



@Entity
public class Movie extends Item{
    private String director;
    private String actor;
}

소스코드

Movie movie = new Movie();
movie.setDirector("봉준호");
movie.setActor("송강호");
movie.setName("괴물");
movie.setPrice(10000);

em.persist(movie);

em.flush();
em.clear();

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

 

조인 전략

@Inheritance(strategy = InheritanceType.JOINED)

장점 

- 테이블 정규화

- 외래키 참조 무결성 제약조건 활용

- 저장공간 효율화

제약조건을 item에 걸어서 맞출 수 있고, 다른 테이블에서 Item이 필요할 때 Item 테이블만으로도 확인이 가능하다.

 

단점

- 조회시 조인을 많이 사용, 성능 저하

- 조회 쿼리가 복잡하며 데이터 저장 시 INSERT SQL 2번 실행

 

Hibernate log

Hibernate:
/* insert hellojpa.Movie */ 
	insert
    into
    Item
    (name, price, id)
    values
    (?, ?, ?)
    
Hibernate:
/* insert hellojpa.Movie */ 
    insert
    into
    Movie
    (actor, director, id)
    values
    (?, ?, ?)

Hibernate:
    select
    movie0_.id as id1_2_0_,
    movie0_1_.name as name2_2_0_,
    movie0_1_.price as price3_2_0_,
    movie0_.actor as actor1_4_0_,
    movie0_.director as director2_4_0_
    from
    Movie movie0_
    inner join
    Item movie0_1_
    on movie0_.id=movie0_1_.id
    where
    movie0_.id=?

실제 로그를 확인해보면 2번의 insert sql이 실행되고, find를 하게 되면 inner join으로 값을 가져온다.

 

 

단일 테이블 전략 (default)

하나의 Item테이블에 서브타입의 컬럼을 전체 관리하며 dtype으로 서브타입을 구분

 

장점

- 조인이 필요 없으므로 일반적으로 조회 성능이 빠르며 조회 쿼리가 단순하다.

 

단점

- 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 한다.

- 단일 테이블에 모든 것을 저장하여 테이블이 커질 수 있으므로 상황에 따라서 조회 성능이 오히려 느려질 수 있다.

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

Hibernate: 
    /* insert hellojpa.Movie
        */ insert 
        into
            Item
            (name, price, actor, director, DTYPE, id) 
        values
            (?, ?, ?, ?, 'Movie', ?)
Hibernate: 
    select
        movie0_.id as id2_0_0_,
        movie0_.name as name3_0_0_,
        movie0_.price as price4_0_0_,
        movie0_.actor as actor8_0_0_,
        movie0_.director as director9_0_0_ 
    from
        Item movie0_ 
    where
        movie0_.id=? 
        and movie0_.DTYPE='Movie

하나의 테이블이 생성되며 insert sql 1번, select도 join이 필요 없기 때문에 성능상 이점이 있지만, Movie와 상관없는 컬럼은 null로 들어간다. 조인 전략에서는 @DiscriminatorColumn 이 없으면 Dtype 생성이 안되는데, 단일 테이블 전략은 필수로 생성이 된다.

 

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

이 전략은 권장하지 않는 전략이다.

 

장점 

- 서브 타입을 명확하게 구분해서 처리할 때 효과적

- not null 제약 조건을 사용할 수 있다.

 

단점

- 여러 자식 테이블을 함께 조회할때 성능이 느리다.(UNION SQL)

- 자식 테이블을 통합해서 쿼리하기 어려움

예를 들어서 album, movie, book으로 정산을 해야 하는 경우 각각 정산을 구해야 한다.

 

item테이블을 사용하지 않고 album, movie, book 각각 별도로 3개의 테이블로 구성

이 전략을 사용하기 위해서는 Item 클래스를 추상(abstract) 클래스로 만들어야 한다.

별도의 클래스로 구성되어 있기 때문에 @DiscriminatorColumn 필요 없다.

 

Hibernate log
Hibernate: 
    /* insert hellojpa.Movie */ 
        insert 
        into
            Movie
            (name, price, actor, director, id) 
        values
            (?, ?, ?, ?, ?)
Hibernate: 
    select
        movie0_.id as id1_2_0_,
        movie0_.name as name2_2_0_,
        movie0_.price as price3_2_0_,
        movie0_.actor as actor1_4_0_,
        movie0_.director as director2_4_0_ 
    from
        Movie movie0_ 
    where
        movie0_.id=?

 

슈퍼타입 Item.class로 조회하는 경우

Item findItem = em.find(Item.class, movie.getId());
Hibernate: 
    select
        item0_.id as id1_2_0_,
        item0_.name as name2_2_0_,
        item0_.price as price3_2_0_,
        item0_.artist as artist1_0_0_,
        item0_.author as author1_1_0_,
        item0_.isbn as isbn2_1_0_,
        item0_.actor as actor1_4_0_,
        item0_.director as director2_4_0_,
        item0_.clazz_ as clazz_0_ 
    from
        ( select
            id,
            name,
            price,
            artist,
            null as author,
            null as isbn,
            null as actor,
            null as director,
            1 as clazz_ 
        from
            Album 
        union
        all select
            id,
            name,
            price,
            null as artist,
            author,
            isbn,
            null as actor,
            null as director,
            2 as clazz_ 
        from
            Book 
        union
        all select
            id,
            name,
            price,
            null as artist,
            null as author,
            null as isbn,
            actor,
            director,
            3 as clazz_ 
        from
            Movie 
    ) item0_ 
where
    item0_.id=?

이 전략은 단순하지만, 부모타입으로 조회를 할 때 union으로 모든 테이블을 합쳐서 조회한다는 단점이 있다.

 

주요 어노테이션

@Inheritance(strategy=InheritanceType.XXX)

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

이 어노테이션을 사용해서 전략을 변경할 수 있다. default는 SINGLE_TABLE

@Inheritance 이 어노테이션 수정만으로 전략을 쉽게 변경할 수 있는 장점이 있다.

 

@DiscriminatorColumn

Dtype을 사용하려면 해당 어노테이션을 추가. 해당 컬럼에서 엔티티가 들어가고, name을 통해서 컬럼명을 수정할 수 있다.

엔티티명이 아니라 원하는 값으로 넣고 싶으면 @DiscriminatorValue("M") 이런 식으로 사용하면 된다.

조인 전략에서는 @DiscriminatorColumn 사용하지 않아도 조인으로 해결할 수 있지만, SINGLE_TABLE에서는 필수로 사용해야 한다.

 

어떠한 전략을 사용하더라도 jpa에서는 구성이 전부 가능하다.

조인 전략을 기본 베이스로 하고 데이터가 단순하고 확장할 필요가 없을 경우엔 단일 테이블 전략을 사용하는 방법을 추천!

 

 

참고 : 자바 ORM 표준 JPA 프로그래밍

반응형