상속 관계 매핑

객체의 상속 관계를 테이블로 표현하는 방법

관계형 데이터베이스는 상속관계를 지원하지 않는다.

대신에 데이터베이스의 슈퍼타입, 서브타입 관계라는 모델링 기법을 통해 객체의 상속 관계를 매핑할 수 있다.(객체 상속과 유사)

image

위 그림은 Item을 슈퍼타입으로 Album, Movie, Book을 서브타입으로 모델링한 것이다.

슈퍼타입과 서브타입이라는 논리 모델을 실제 물리 모델(테이블)로 구현하는 방법은 3가지가 있다.

  • 조인 전략: 각각 테이블로 변환

  • 단일 테이블 전략: 통합 테이블로 변환

  • 구현 클래스마다 테이블 전략: 서브 타입 테이블로 변환

3가지 방법 모두 JPA를 통해 구현할 수 있다.

주요 어노테이션

  • @Inheritance(strategy=InheritanceType.xxx): 부모 클래스에서 사용

    • 매핑 전략 지정

    • InheritanceType.JOINED: 조인 전략

    • InheritanceType.SINGLE_TABLE: 단일 테이블 전략

    • InheritanceType.TABLE_PER_CLASS: 구현 클래스 마다 테이블 전략

  • @DiscriminatorColumn(name=”DTYPE”): 부모 클래스에서 사용

    • 조인 전략, 단일 테이블 전략에서 필수

    • default name이 “DTYPE”이다. 그러므로 생략 가능한데, 원하는 값으로 지정 가능하지만 기본 값을 그대로 쓰는 것을 추천한다.

    • 부모 클래스와 각 자식 클래스의 구분 컬럼을 설정한다.

    • 구분 컬럼을 통해 각 행이 어떤 서브타입의 데이터인지 식별하는 역할

  • @DiscriminatorValue(”xxx”): 자식 클래스에서 사용

    • 각 자식 클래스가 구분 컬럼에 사용할 값(Value)을 설정한다.

    • Value는 해당 자식 클래스가 나타내는 타입을 식별하는데 사용된다.

    • “Book”은 자식 클래스가 “Book” 타입임을 나타낸다.

조인 전략

image

조인 전략은 슈퍼타입, 서브타입 논리 모델을 각각 테이블로 옮긴 방식이다.

테이블이 구분되어 있기 때문에 데이터를 조회할 때 조인이 필요해서 조인 전략이라고 부른다.

장점

  • 테이블 정규화에 유리

  • 외래키 참조 무결성 제약 조건 활용 가능

  • 저장공간 효율적으로 사용

단점

  • 조회 시 JOIN을 많이 사용하기에 성능 저하 → 컴퓨터 성능이 좋아져서 데이터가 엄청 많지 않은 이상 성능이 엄청 떨어지지는 않는다.

  • 쿼리가 복잡하다.

  • 데이터 저장시 INSERT SQL 2번 호출한다.

조인 전략은 상속관계를 매핑하는 전략 중 데이터베이스 관점에서 가장 정규화된 깔끔하고 정석적인 전략이다.

코드로 살펴보면,

@Entity
@Getter
@Inheritance(strategy = InheritanceType.JOINED)//부모 클래스
@DiscriminatorColumn(name = "DTYPE")
public class Item {

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

@Entity
@Getter
@DiscriminatorValue("BOOK")//자식 클래스
public class Book extends Item {

    @Id
    @GeneratedValue
    private Long id;
    private String author;
    private String isbn;
}

서브타입 Album은 슈퍼타입 Item을 상속 받는다.

@DiscriminatorValue()를 통해 구분 컬럼 값을 정의한다. 기본 값은 엔티티 이름이다.

SINGLE_TABLE 전략

image

조인 전략이 슈퍼타입과 서브타입을 정규화해서 각각 다른 테이블에 넣었던 방식이면,

단일 테이블 전략은 자식 클래스(서브타입)의 모든 속성들을 부모 테이블에 다 때려 넣는 테이블을 생성한다.

서비스 규모가 크지 않아 단순하게 해도 상관 없을 때 사용하기 좋다.

장점

  • 조인이 필요 없어서 일반적으로 조회 성능이 빠르고, 쿼리가 단순하다.

단점

  • 자식 클래스의 컬럼들은 모두 null을 허용해야 하고, 모든 내용을 다 저장한다.

    → 속성들이 많으면 테이블이 커질 수 있어서 상황에 따라서 조회 속도가 오히려 느려진다.

코드로 살펴보면,

@Entity
@Getter
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)//부모 클래스
@DiscriminatorColumn(name = "DTYPE")
public class Item {

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

@Entity
@Getter
@DiscriminatorValue("BOOK")//자식 클래스
public class Book extends Item {

    private String author;
    private String isbn;
}

그리고 이런식으로 구현만 했을때도 SINGLE_TABLE 전략으로 실행된다.

@Entity
@Getter
@NoArgsConstructor
public class Item {

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

@Entity
@Getter
@NoArgsConstructor
public class Book extends Item {

    @Id
    @GeneratedValue
		private Long id;
    private String author;
    private String isbn;
}

@Entity
@Getter
@NoArgsConstructor
public class Movie extends Item {

  @Id
  @GeneratedValue
  private Long id;
  private String director;
  private String actor;

}

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

image

슈퍼 타입(부모)은 테이블로 매핑하지 않고, 서브 타입(자식)만 테이블로 매핑하는 방법이다.

장점

  • 서브 타입만 명확하게 구분해서 처리할 수 있다.

  • 단일 테이블 전략에서는 불가능한 not null 제약조건을 사용할 수 있다.

단점

  • 여러 자식 테이블을 함께 조인할 때 union 연산을 사용해야 해서 성능이 엄청 느리다.

    union 연산: Select 2개를 해서 결과 2개를 중복된 값을 제거해서 하나로 합치는 것이다.

  • 자식 클래스가 추가되면 조회 코드를 변경해야 한다.

이 방법은 DB, JPA에서도 추천하지 않는 방법이다.

정리하자면,

조인 전략은 테이블을 정규화한 정서적이고, 이상적인 전략이기에 데이터가 많고 무결해야 하는 경우 사용한다.

단일 테이블 전략은 테이블을 다루기 편리한 실용적인 전략이기에 데이터의 수가 많지 않을 때 사용한다.

즉, 상황에 따라서 둘 중 하나를 선택하면 된다.

구현 클래스마다 테이블 전략은 사용하지 말자!

공통 속성이 존재할 때(@MappedSuperClass)

image

위 그림처럼 객체 지향에서 속성 재사용을 위해 상속을 할 때 사용할 수 있다.

공통으로 쓰는 속성을 물려받게 설계를 할 수 있다.

단 이걸 사용할 때 상속 관계 매핑과 혼동의 오면 안된다. 상속 관계 매핑처럼 종속 시키는게 아닌 단순히 속성을 재사용하기 위해 상속을 사용하는 것이다.

그리고, 엔티티가 아니기에 상위 클래스로 조회할 수 없다. 객체화 할 일이 없어서 추상 클래스로 만드는 것을 권장한다.

아래 코드처럼 프로젝트에서 흔히 쓰이는 코드이다.

@MappedSuperclass
@Getter @Setter
public abstract class BaseEntity {

    private String createdBy;
    private LocalDateTime createdAt;
    private LocalDateTime lastModifiedBy;
    private LocalDateTime lastModifiedAt;
}

참고 자료

Last updated