Spring/JPA

고급매핑 - 상속관계 매핑

느리지만 꾸준하게 2022. 5. 27. 10:14

Album, Book, Item, Movie를 등록해주고

 

Album

package hellojpa;

import javax.persistence.Entity;

@Entity
public class Album extends Item{
    private String artist;
}

 

Book

package hellojpa;

import javax.persistence.Entity;

@Entity
public class Book extends Item{

    private String author;
    private String isbn;
}

 

Item

package hellojpa;
import javax.persistence.*;

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
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;
    }
}

 

Movie

package hellojpa;
import javax.persistence.Entity;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;

@Entity
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;
    }
}

 

 

 

JpaMain

package hellojpa;

import javax.management.relation.Role;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;

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.setDirector("aaaa");
            movie.setActor("bbbb");
            movie.setName("바람과함께사라지다");
            movie.setPrice(10000);

            em.persist(movie);

            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    /* insert hellojpa.Movie
        */ insert 
        into
            Item
            (name, price, id) 
        values
            (?, ?, ?)
Hibernate: 
    /* insert hellojpa.Movie
        */ insert 
        into
            Movie
            (actor, director, id) 
        values
            (?, ?, ?)

 

조회를 해보면

try {

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

            em.persist(movie);

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

            Movie findMovie = em.find(Movie.class, movie.getId());
            System.out.println("findMovie = " + findMovie);

            tx.commit();

 

join이 필요할 때 join을 해주게 된다.(item과 movie)

Hibernate: 
    call next value for hibernate_sequence
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_6_0_,
        movie0_.director as director2_6_0_ 
    from
        Movie movie0_ 
    inner join
        Item movie0_1_ 
            on movie0_.id=movie0_1_.id 
    where
        movie0_.id=?
findMovie = hellojpa.Movie@2f860823

 

 

Item 테이블에 어노테이션을 넣어주면 Item 부분에 Dtype 이 생긴다.

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

 

 

 

그리고 Movie 부분에 DiscriminatorValue("M")을 넣어줘서 구분해줄 수도 있다.

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

 

 

단일테이블 전력으로 바꾸려면 이렇게 해주자.

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

insert와 join을 할 필요가 없다.

Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    /* insert hellojpa.Movie
        */ insert 
        into
            Item
            (name, price, actor, director, DTYPE, id) 
        values
            (?, ?, ?, ?, 'M', ?)
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='M'
findMovie = hellojpa.Movie@a619c2

 

그리고 join 전략이 아닌 이 단일테이블 전략에서 

@DiscriminatorColumn이 없어도 Dtype이 생기게 된다.

@DiscriminatorColumn
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)
    )

 

 

결론 적으로는 쿼리 한번 조회 한번이 생기게 된다.

package hellojpa;

import javax.management.relation.Role;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;

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.setDirector("aaaa");
            movie.setActor("bbbb");
            movie.setName("바람과함께사라지다");
            movie.setPrice(10000);

            em.persist(movie);

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

            Movie findMovie = em.find(Movie.class, movie.getId());
            System.out.println("findMovie = " + findMovie);

            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    /* insert hellojpa.Movie
        */ insert 
        into
            Item
            (name, price, actor, director, DTYPE, id) 
        values
            (?, ?, ?, ?, 'M', ?)
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='M'
findMovie = hellojpa.Movie@bcb09a6

 

 

구현 클래스마다 테이블 전략을 보자.(실무에서 쓰면 안됨,)

Item을 추상클래스로 만들어주면 create에 item이 없다.

package hellojpa;
import javax.persistence.*;

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@DiscriminatorColumn
public abstract 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;
    }
}

 

조인 전략을 살펴보자.

 

장점으로

  • 테이블 정규화
  • 외래 키 참조 무결성 제약조건 활용가능
  • 저장공간 효율화

 

단점으로

  • 조회시 조인을 많이 사용, 성능 저하
  • 조회 쿼리가 복잡
  • 데이터 저장시 INSERT SQL 2번 호출

 

 

단일 테이블 전략도 보자.

 

장점으로

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

 

단점으로

  • 자식 엔티티가 매핑한 컬럼은 모두 null 허용되고
  • 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있고 상황에 따라 조회 성능이 느려질 수 있다.

 

 

 

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

  • 일단 쓰면 안되는 전략이고

장점으로는

  • 서브 타입을 명확하게 구분해서 처리할 때 효과적이고
  • not null 제약조건 사용 가능

 

단점으로

  • 여러 자식 테이블을 함께 조회할 때 성능이 느리다.(UNION SQL)
  • 자식 테이블을 통합해 쿼리하기 어렵다.

 

 

JPA에서의 주요 어노테이션을 보자.

  • Inheritance(strategy = InheritanceType.XXX)
  • JOINED: 조인 전략
  • SINGLE_TABLE: 단일 테이블 전략
  • TABLE_PER_CLASS: 구현 클래스마다 테이블 전략

 

  • @DiscriminatorColumn(name="DTYPE")
  • @DiscriminatorValue("XXX")

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

<출처 김영한: 자바 ORM 표준 JPA 프로그래밍 - 기본편 >

https://www.inflearn.com/course/ORM-JPA-Basic/dashboard

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com