Spring/JPA

고급 매핑 - 상속관계 매핑

Ynghan 2023. 2. 8. 16:05

목차

  • 상속관계 매핑
객체의 상속관계를 테이블에 어떻게 매핑하는지 알아본다.
  • @MappedSuperclass
속성만 상속하는 것이다.
  • 실전 예제 - 4. 상속관계 매핑
이러한 상속관계 매핑을 어떻게 사용하는지 알아본다.

 

상속관계 매핑


  1. 관계형 데이터베이스는 상속 관계 X
  2. 슈퍼타입 서브타입 관계라는 모델링 기법이 객체 상속과 유
  3. 상속관계 매핑 : 객체의 상속과 구조와 DB의 슈퍼타입 서브타입 관계를 매핑슈퍼타입 서브타입 논리 모델을 실제 물리 모델로 구현하는 방법DB입장에서 3가지 방법으로 구현할 수 있다.
    • 각각 테이블로 변환 -> 조인 전략
    • 통합 테이블로 변환 -> 단일 테이블 전략
    • 서브타입 테이블로 변환 -> 구현 클래스마다 테이블 전략
  •  

조인 전략

데이터를 가져올 때 JOIN으로 가져오는 것, 총 2번의 INSERT, 조회는 PK와 FK를 JOIN 해서 가져온다.
ITEM 테이블만 보면 ALBUM인지 MOVIE인지 BOOK인지 알 수 없기 때문에 ITEM 테이블에 DTYPE을 두어 테이블을 구분하는 Column을 둔다.

단일 테이블 전략

Column을 ITEM 테이블에 전부 넣고 DTYPE으로 자식 테이블을 구분한다.

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

 

ITEM 테이블을 없애고 각각의 자식 테이블에 모두 공통된 Column까지 넣는다.
DB에서 3가지 중 어떤 방식으로 구현해도 JPA에서는 전부 매핑이 가능하다.

코드

JpaMain.java
        try{
            Movie movie = new Movie();
            movie.setDirector("aaa");
            movie.setActor("bbb");
            movie.setName("바람과함께사라지다");
            movie.setPrice(10000);

            em.persist(movie);
            ...
        }

조인 전략 - 코드

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Item {
	@Id @GeneratedValue
    private Long id;
    
    private String name;
    private int price;
}
strategy = InheritanceType.JOINED

DTYPE 나타내는 코드

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
public class Item {
...
}
DiscriminatorColumn을 넣어주면 DTYPE이 생기고 엔티티 명이 들어간다.

DTYPE이 없다면 DB에 쿼리를 날릴 때 이게 어떤 엔티티에 의해 듣어온 것인지 모른다.

@DiscriminatorColumn(name = "aaa")를 사용하여 DTYPE의 이름을 변경할 수도 있다.

DTYPE의 Column 값인 엔티티명을 바꾸고 싶다면 (예를 들어, Movie를 M으로 바꾸고 싶다면)
Movie 엔티티 클래스에 @DiscriminatorValue("M")을 작성한다. 

@DiscriminatorValue("M") 사용전(왼쪽), 사용후(오른쪽)

단일 테이블 전략 - 코드

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn
public class Item {
	@Id @GeneratedValue
    private Long id;
    
    private String name;
    private int price;
}
strategy = InheritanceType.SINGLE_TABLE

모든 Column이 하나의 테이블에 들어간다.

단일 테이블 전략은 @DiscriminateColumn이 없어도 DTYPE이 있다.

왜냐하면 단일 테이블 전략은 서브타입을 알 수 있는 방법이 없기 때문에 DTYPE을 필수로 넣기때문이다.

DTYPE은 항상 있는 것이 좋다.

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

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@DiscriminatorColumn
public class Item {
	@Id @GeneratedValue
    private Long id;
    
    private String name;
    private int price;
}

해당 전략에서는 @DiscriminateColumn을 사용할 이유가 없다.

서로 구분할 필요가 없어진 것이다.

테이블 자체가 다르다.

해당 전략은 단순히 값을 넣었다가 뺄 경우에는 좋지만
그 이외의 작업에서는 해당 슈퍼 타입 테이블이 있는지 없는지 알아보기 위해서는
모든 서브 타입들을 SELECT 해봐야한다.

각각의 장단점

조인 전략

  • 장점
    • 테이블의 정규화
    • 외래 키 참조 무결성 제약조건 활용가능
    • 저장 공간의 효율화
  • 단점
    • 조회시 조인을 많이 사용, 성능 저하
    • 조회 쿼리가 복잡함
    • 데이터 저장시 INSERT SQL 2번 호출함
정석인 전략이다.

단일 테이블 전략

  • 장점
    • 조인이 필요 없으므로 일반적으로 조회 성능이 빠름
    • 조회 쿼리가 단순함
  • 단점
    • 자식 엔티티가 매핑한 컬럼은 모두 null 허용
    • 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있고, 상황에 따라서 조회 성능이 오히려 느려질 수 있다.

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

사용하면 안되는 전략이다.
이 전략은 데이터베이스 설계자와 ORM 전문가 둘 다 추천하지 않는다.
  • 장점
    • 서브 타입을 명확하게 구분해서 처리할 때 효과적 (INSERT, SELECT)
    • not null 제약조건 사용 가능
  • 단점
    • 여러 자식 테이블을 함께 조회할 때 성능이 느림(UNION SQL)
    • 자식 테이블을 통합해서 쿼리하기 어려움

예를 들어, 정산 비지니스 로직이 필요한 경우

조인 전략이나, 단일 테이블 전략은 PRICE만 계산하기 때문에 ITEM 테이블만 건들이면 된다.
그러나, 구현 클래스마다 테이블 전략은 PRICE를 계산하기 위해서 모든 테이블을 건들여야 하고,
또 새로운 정산 로직이 추가될 경우에 모든 테이블이 수정되어야 하는 번거로움이 생긴다.
JOIN 전략을 기본으로 하되 단일 테이블 전략을 사용하기 좋을 경우에 바꿔가며 사용하자.