본문 바로가기

Spring/JPA

상속관계 매핑

상속 관계 매핑

왼쪽이 관계형 DB의 슈퍼타입 서브타입 논리 모델, 오른쪽이 객체 상속 모델

 

객체를 가지고 개발하다 보면 상속 개념을 자주 사용합니다. 하지만 DB 테이블에는 정확히 객체의 상속 관계와 동일한 상속 개념이 없습니다. 슈퍼타입 서브타입 관계라는 모델링 기법이 객체 상속과 유사한 개념입니다. 따라서 JPA에서 객체의 상속(엔티티 상속)과 테이블의 슈퍼타입 서브타입 관계를 매핑하는 것을 상속 관계 매핑이라 부릅니다.

 

객체의 상속 관계를 테이블의 슈퍼타입 서브타입 관계로 매핑할 때 JPA는 세 가지 방법을 제공합니다. 각각의 테이블로 변환하는 조인 전략, 통합 테이블로 변환하는 단일 테이블 전략, 그리고 서브타입 테이블로 변환하여 구현 클래스마다 테이블을 만드는 전략 이렇게 세 가지 전략을 제공합니다.

 

상속 관계 매핑 애노테이션

상속 관계 매핑에 사용되는 애노테이션은 @Inheritance(strategy = InheritanceType.XXX)가 대표적입니다. 부모 클래스에 @Inheritance를 붙이므로 상속 관계 매핑을 하는 부모 자식 클래스임을 나타냅니다.

 

@Inheritance 애노테이션을 부모 클래스에 붙이고 strategy JOINED(조인 전략), SINGLE_TABLE(단일 테이블 전략), TABLE_PER_CLASS(구현 클래스마다 테이블 전략) 설정할 있습니다. 결론부터 이야기하면 JOINED 전략을 기본적으로 사용하고 설계가 단순하고 확장될 가능성이 적을 SINGLE_TABLE 전략을 사용합시다. TABLE_PER_CLASS 전략은 사용을 지양해야 합니다

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

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

 

@Inheritance 외에도 @DiscriminatorColumn @DiscriminatorValue 애노테이션도 사용됩니다. 아래에서 살펴봅시다.

 

조인 전략 (InheritanceTpye.JOINED)

 

조인 전략은 객체의 상속 구조와 유사하게 클래스를 부모 테이블과 자식 테이블들로 변환한 전략입니다. 자식 테이블을 조회하면 부모 클래스까지 조인하여 반환하고 자식 테이블을 추가할 때는 자식 테이블과 부모 테이블을 모두 INSERT 해야 합니다.

 

조인 전략을 사용해서 상속 구조를 테이블에 매핑하면 ITEM 부모 테이블에 DTYPE을 추가해야 합니다. DTYPE은 자식을 구분하는 용도로 사용되기 때문에 Album 객체를 Item 부모 타입으로 접근했을 때 해당 자식 타입을 알 수 있게 됩니다. 부모 타입의 테이블에 DTYPE을 추가할 때는 직접 필드를 추가하지 않고 부모 클래스에 @DiscriminatorColumn 애노테이션을 붙여 추가합니다. 부모 클래스 Item에 @DiscriminatorColumn을 추가하면 ITEM 테이블에 DTYPE을 만들고 자식 엔티티 클래스 명을 값으로 가집니다. 운영 측면에서 DTYPE을 부모 테이블에 넣는 걸 추천합니다.  

 

조인 전략을 사용한 상속 관계 매핑을 코드로 살펴봅시다.

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

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

@Entity
@DiscriminatorValue("A")
public class Album extends Item {

    private String artist;
}

@Entity
public class Movie extends Item {

    private String director;
    private String actor;
}

@Entity
public class Book extends Item {

    private String author;
    private String isbn;
}

 

부모 엔티티 클래스에 @Inheritance를 붙이고 DTYPE을 위해 @DiscriminatorColumn을 붙였습니다. 조인 전략에서는 DTYPE이 선택 사항이기 때문에 @DiscriminatorColumn을 붙여야 DTYPE을 부모 테이블에 추가할 수 있습니다. 자식 엔티티 클래스에서 Item 클래스를 상속받고 있습니다. 자식 엔티티 클래스에서 @DiscriminatorValue를 사용하면 부모 테이블에 저장될 DTYPE 명을 직접 지정할 수 있습니다. 지정하지 않을 경우 엔티티 클래스 명이 기본으로 설정됩니다.

Item 클래스를 추상 클래스(abstract)로 만들어서 상속과 관계없이 단독으로 사용되는 일을 막을 수 있습니다.

 

조인 전략은 정규화된 테이블 구조로 설계할 있고 외래 참조 무결성 제약 조건을 활용 가능합니다. 주문이나 다른 테이블에서 자식 엔티티 테이블까지 들어가지 않아도 ITEM 테이블에 접근하여 설계를 깔끔하게 만들 있습니다. 하지만 조회 조인을 많이 사용하고 데이터 저장 INSERT 쿼리를 자식 테이블과 부모 테이블로 호출해야 하기 때문에 성능 저하로 이어집니다. 하지만 성능적으로 크게 저하되진 않기 때문에 상속 관계 매핑 전략은 조인 전략을 정석으로 생각하고 설계하면 좋습니다.

 

단일 테이블 전략 (InheritanceType.SINGLE_TABLE)

 

두 번째로 살펴볼 상속 관계 매핑 전략은 단일 테이블 전략입니다. 단일 테이블 전략은 @Inheritance 애노테이션이 기본적으로 따르는 전략으로 별도의 전략의 strategy 속성으로 설정하지 않으면 단일 테이블 전략을 수행합니다. 단일 테이블 전략은 하나의 테이블에 모든 자식 엔티티의 필드를 저장하고 DTYPE의 활용해 자식 엔티티의 타입을 구분하는 방식입니다.

 

단일 테이블 전략에서 DTYPE은 필수적으로 요구되기 때문에 조인 전략 때와는 달리 부모 클래스에 @DiscriminatorColumn 애노테이션을 붙이지 않아도 DTYPE을 자동으로 생성합니다. 단일 테이블 전략으로 구현된 ITEM 테이블에 Movie 타입 엔티티를 저장하게 되면 아래와 같이 Movie 엔티티의 필드를 제외한 나머지 필드들은 null이 됩니다.

 

단일 테이블 전략을 사용하면 조회 조인이 필요하지 않기 때문에 일반적으로 조회 성능이 좋습니다. 당연히 조회 쿼리가 조인 전략보다 단순합니다. 하지만 자식 엔티티가 매핑한 컬럼은 모두 null 허용하기 때문에 치명적일 있습니다. 또한 단일 테이블에 모든 것을 저장하다 보니 테이블이 커질 있어서 조회 성능이 오히려 떨어질 있습니다.

 

구현 클래스마다 테이블 전략 (InheritanceType.TABLE_PER_CLASS)

 

부모 클래스를 구현하는 자식 클래스마다 테이블을 생성하는 전략입니다. 기존 전략에서의 부모 테이블을 없애고, 부모 테이블의 속성들을 자식 테이블로 내린 전략입니다. 때문에 자식 테이블들은 중복된 속성을 가지고 있습니다. 

 

이 전략은 쓰면 안 됩니다. not null 제약 조건(무조건 값이 있어야 함)을 사용할 수 있지만 부모 타입인 Item 타입으로 조회할 때 모든 테이블을 다 뒤져야 하기 때문에 성능이 굉장히 안 좋습니다. UNION SQL을 사용해서 union all select 해야 합니다. 또한 변경 사항이 생겼을 때 너무 많은 포인트에서 수정이 발생합니다.

 

결론

지금까지 세 가지 상속 관계 매핑 전략을 살펴보았습니다.

결론적으로 기본은 조인 전략을 사용하고 구조가 단순하거나 데이터가 얼마 없거나 확장할 가능성이 적을 단일 테이블 전략을 고려해 봅시다.

'Spring > JPA' 카테고리의 다른 글

프록시 (Proxy)  (0) 2024.10.21
@MappedSuperclass  (0) 2024.10.17
일대일 연관관계 매핑  (1) 2024.10.17
연관관계 매핑 기초  (1) 2024.10.17
기본키 매핑  (0) 2024.10.17