Programming/Java

[JPA] JPA에서 DB 슈퍼-서브 타입 구현하는 방법

kmindev 2024. 12. 20. 10:41

1. 개요

김영한 님의 '자바 ORM 표준 JPA 프로그래밍 - 기본편' 을 들으면서 정리하는 포스팅입니다.

 

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

 

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

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

www.inflearn.com

 

 

2. DB 슈퍼-서브 타입 관계 모델링

객체지향에서 상속 관계를 사용하면 공통된 필드를 슈퍼 타입에 정의하여 코드의 중복을 줄일 수 있다는 장점이 있다.

하지만, 관계형 데이터베이스는 상속 관계를 직접적으로 지원하지 않는다.(지원하는 DB도 있음.)

 

관계형 데이터베이스에서는 상속 구조와 유사한 슈퍼-서브 타입 관계의 모델링 기법이 있다.

 

관계형 데이터베이스에서 슈퍼-서브 타입 관계는 공통된 속성을 가진 엔티티를 슈퍼타입으로 정의하고, 이를 상속 받는 서브 타입을 별도로 정의하는 방식으로 모델링 할 수 있다. 이를 통해 데이터 구조의 논리적 표현이 명확해지고, 중복 데이터를 줄일 수 있다.

 

예를 들어 앨범, 영화, 책을 표현하기 위해 각각의 테이블을 생성해서 모델링을 할 수 있다.

하지만, 이들 간에는 공통된 속성(name, price)이 존재한다.

 

위 상황에서는  어떤식으로 풀어나갈지는 상황에 따라 유연하게 대처할 필요가 있다. 

이를 해결하기 위한 슈퍼-서브 타입 관계의 3가지 전략에 대해서 알아보자.

 

 

3. 슈퍼-서브 타입 전략 설정 @Inheritance

 JPA에서는 @Inheritance 어노테이션을 사용하여 슈퍼-서브 타입 관계를 정의할 수 있다.

이를 통해 데이터베이스 슈퍼-서브 타입 모델링 구조를 구현할 수 잇다.

 

a. joined 전략(조인 전략)

a-1. Joined 전략 특징

  • 슈퍼타입과 서브타입 각각에 대해 별도의 테이블을 생성한다.
  • 데이터 중복을 최소화 한 정규화된 방식이다.
  • 슈퍼타입 관점과 서브타입 관점에서 각각 유연하게 조회할 수 있다.
  • 조회시 조인을 많이 사용하므로, 성능이 저하될 수 있다.
  • 데이터 저장시 Insert가 두번 호출된다.(Item, Alume 각각 Insert)

 

a-2. Joined 전략 예제

@DiscriminatorColumn // DTYPE(자식 타입을 구분하는 컬럼 생성)
@Inheritance(strategy = InheritanceType.JOINED) // DB 슈퍼타입, 서브타입 전략 설정
@Entity
public class Item {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private int price;
    
    // Getter, Setter
}

@DiscriminatorValue("Album") // 슈퍼 타입을 DTYPE 컬럼의 Value를 지정(defaut: Entity 클래스 이름)
@Entity
public class Album extends Item {
    private String artist;
    
    // Getter, Setter
}

@DiscriminatorValue("Book") // 슈퍼 타입을 DTYPE 컬럼의 Value를 지정(defaut: Entity 클래스 이름)
@Entity
public class Book extends Item {
    private String author;
    private String isbn;

    // Getter, Setter
}

 

DDL 결과

  • Item(슈퍼타입) 테이블과 Album, Book(서브타입) 테이블을 생성한다.
  • 슈퍼타입과 서브타입을 FK(외래키)로 맺어준다.

 

a-3. Joined 전략에서 Insert - Select 예제 

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());
System.out.println("findMovie = " + findMovie);

 

Insert-Select 결과

  • Movie(서브타입)를 Insert 할 때 Item(슈퍼타입)의 Insert 쿼리도 발생한다.
  • 조회시 Join을 사용한다.

 

b. single table전략 (단일 테이블 전략)

b-1. single table 전략 특징

  • 모든 데이터를 하나의 테이블에 저장하고, 타입 컬럼을 추가하여 레코드의 유형을 구분한다.
  • 하나의 테이블로 관리가 간단함.
  • Join이 필요하지 않아 성능적으로 유리할 수 있음.
  • NULL 데이터가 많아짐.(서브 타입의 엔티티 컬럼은 모두 NULL 허용)
  •  서브타입이 늘어나면 테이블이 너무 커질 수 있다.(오히려 성능 저하가 발생)

 

b-2. single table 전략 예시

@DiscriminatorColumn // DTYPE(자식 타입을 구분하는 컬럼 생성) 싱글 테이블 전략에서 생략 가능
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) // DB 슈퍼타입, 서브타입 전략 설정
@Entity
public class Item {

    @Id
    @GeneratedValue
    private Long id;

    private String name;
    private int price;
    
    // Getter, Setter
}

@DiscriminatorValue("Album") // 슈퍼 타입을 DTYPE 컬럼의 Value를 지정(defaut: Entity 클래스 이름)
@Entity
public class Album extends Item {
    private String artist;
    
    // Getter, Setter
}

@DiscriminatorValue("Book") // 슈퍼 타입을 DTYPE 컬럼의 Value를 지정(defaut: Entity 클래스 이름)
@Entity
public class Book extends Item {
    private String author;
    private String isbn;

    // Getter, Setter
}

 

DDL 결과

  • 서브타입의 모들 속성들을 Item(슈퍼타입)에 저장한다.

 

Insert - Select 결과

  • Joined 전략과 다르게 한번만 Insert 한다는 점과 조회할 때 join을 사용하지 않는다.

 

 

c. table per class 전략(구현 클래스마다 테이블)

c-1. table per class 전략 특징

  • 슈퍼 타입 테이블 없이 서브 타입마다 개별 테이블을 생성함.
  • 각 테이블은 독립적
  • 공통된 속성에 대한 중복 정의가 발생함.
  • 슈퍼 타입 관점에서 조회하면 UNION 쿼리가 발생함.
  • 자식 테이블을 통합해서 쿼리하기 어려움.

c-2. table per class 전략 예시

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) // DB 슈퍼타입, 서브타입 관계
@Entity
public abstract class Item { // 추상클래스

    @Id
    @GeneratedValue
    private Long id;

    private String name;
    private int price;
    
    // Getter, Setter
}

DDL 결과

  • Item(슈퍼타입) 테이블을 생성하지 않고, 각각의 서브타입 테이블을 생성한다.

 

c-3. table per class 전략 조회쿼리 예시

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

em.persist(movie);

em.flush();
em.clear();
            
Item findItem = em.find(Item.class, movie.getId());
System.out.println("findItem = " + findItem);

 

조회 결과

  • UNION 쿼리를 사용하면서, 모든 서브 타입을 탐색한다.(비효율적)