JPA 에서 상속 관계 구현하기

JPA에서 상속 관계를 구현하는 다양한 전략과 `@DiscriminatorColumn`, `@DiscriminatorValue` 어노테이션 활용법을 자세히 알아봅니다.

관계형 데이터베이스에서는, 상속형 데이터 타입을 지원하지 않는다. 만약 상속을 구현하려면, 논리적 모델에서 적용시킨 뒤에 물리적 모델에서는 변경된 형태로 구현해야 한다. 이와 달리, Java 에서는 상속 관계를 유용하게 사용한다. JPA 는 이러한 RDBMS 와 Java 의 간극을 해소시켜 준다.

 

0. JPA Inheritance 의 종류

InheritanceType.JOINED (기본적으로 이것을 디폴트로 사용한다)

- 공유할 수 있는 Column 은 부모 테이블에 저장하고, 나머지 컬럼은 자식 Table 에 저장한다.

- 정규화 되어있는 형태 -> 저장 공간 효율화

- SELECT 할 때 JOIN 을 많이 해야 한다.

- 데이터 저장시 INSERT 쿼리 두 번 실행

 

InheritanceType.SINGLE_TABLE

- 단일 테이블 전략, 어떠한 타입인지 구별할 DTYPE 이 필수적으로 필요하다.

- 자식 테이블이 매핑한 컬럼은 모두 Null 을 허용해야 한다.

 

InheritanceType.TABLE_PER_CLASS (가급적 사용 X)

- 구현 클래스마다 테이블 전략, 컬럼을 공유하지 않고, 각각의 테이블에 모두 속성을 저장시킨다.

- 데이터 Insert 할 때에는 좋지만, SELECT 할 때에는 자식 테이블마다 조회해서 UNION 하므로, 성능 이슈가 있다.

 

1. Item Class (Parent)

package hellojpa.entity;

import javax.persistence.*;

@Entity
public class Item {

    @Id @GeneratedValue
    private Long id;

    private String name;
    private int price;

}

 

2. Book Class (Child)

package hellojpa.entity;

import javax.persistence.Entity;

@Entity
public class Book extends  Item {

    private String author;
    private String isbn;

}

 

기본적인 클래스만 구현해서 JPA 를 돌려보면, 

    create table Item (
       DTYPE varchar(31) not null,
        id bigint not null,
        name varchar(255),
        price integer not null,
        author varchar(255),
        isbn varchar(255),
        primary key (id)
    )

 

Hibernate 가 Item 테이블에 모두 다 넣어버린다. Hibernate 의 기본 전략이 한 테이블에서 관리하기 때문이다. 만약 별도 테이블로 관리하기 위해서는

Inheritance Strategy 를 JOINED 로 설정해야 한다.

package hellojpa.entity;

import javax.persistence.*;

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

    @Id @GeneratedValue
    private Long id;

    private String name;
    private int price;

}

 

Hibernate: 

    create table Book (
       author varchar(255),
        isbn varchar(255),
        id bigint not null,
        primary key (id)
    )

Hibernate: 

    create table Item (
       id bigint not null,
        name varchar(255),
        price integer not null,
        primary key (id)
    )

Hibernate 는 두 개의 테이블로 관리하게 된다.

 

이 상태에서 Insert 를 시켜보면, 

package hellojpa;

import hellojpa.entity.Movie;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {

            Movie movie = new Movie();
            movie.setActor("Actor");
            movie.setDirector("Director");
            movie.setName("바람과 함께 사라지다");
            movie.setPrice(10000);

            em.persist(movie);

            tx.commit();

        } catch (Exception e) {

            tx.rollback();

        } finally {

            if(em.isOpen()) {
                em.close();
            }

        }

        if(emf.isOpen()) {
            emf.close();
        }
    }
}

 

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

 

Hibernate 에서는 똑똑하게 Item, Movie 테이블에 모두 Insert 를 실행하는 것을 확인할 수 있다.

 


* Discriminator

Item 테이블에서, 각 Row 가 어떤 타입인지 나타내기 위해서는 @DiscriminatorColumn, @DiscriminatorValue 어노테이션을 활용한다.

package hellojpa.entity;

import javax.persistence.*;

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

    @Id @GeneratedValue
    private Long id;

    private String name;
    private int price;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

 

package hellojpa.entity;

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;

@Entity
@DiscriminatorValue("MOVIE")
public class Movie extends  Item {

    private String director;
    private String actor;

    public String getDirector() {
        return director;
    }

    public void setDirector(String director) {
        this.director = director;
    }

    public String getActor() {
        return actor;
    }

    public void setActor(String actor) {
        this.actor = actor;
    }
}

 

돌려보면, Item 테이블에 DTYPE 컬럼이 추가되게 된다.

Hibernate: 
    /* insert hellojpa.entity.Movie
        */ insert 
        into
            Item
            (name, price, DTYPE, id) 
        values
            (?, ?, 'MOVIE', ?)

 


이것도 읽어보세요