[JPA] Inheritance 전략

DevHwan·2022년 12월 8일
1

상속

SQL(데이터베이스)와 객체지향언어가 갖는 큰 차이점 중 하나가 바로 상속의 유무 여부이다. 객체지향언어에서는 상속을 지원하지만, 데이터베이스에서는 상속을 지원하지 않는다.

스프링에서는 JPA를 이용하여 ORM 기술을 사용하는데, 클래스 간 상속 관계에 대해서는 어떻게 정의를 하는 지 알아보자.

ORM에서는 상속 관계 매핑에 대해 객체의 상속구조를 데이터베이스의 슈퍼타입 서브타입 관계를 매핑하는 것으로 대체한다.

물리 모델일 데이터 베이스 테이블로 구현하는 방법에는 크게 4가지 방법이 있다.

  • MappedSuperclass
  • Single Table 전략
  • Joined Table 전략
  • Table per Class 전략

각각의 방법에 대해서 알아보자.

MappedSuperclass

MappedSuperclass 어노테이션은 부모클래스를 데이터베이스의 테이블로 매핑하지 않고 자식클래스에게 매핑 정보를 제공하고 싶을 때만 사용한다.

@MappedSuperclass
public class Person {

    @Id
    private long personId;
    private String name;
}

해당 클래스는 자체적으로 엔티티가 아니다. 따라서 Entity 어노테이션이 없고, 데이터베이스에 테이블의 형태로 존재하지 않는다.

@Entity
public class MyEmployee extends Person {
    private String company;
}

데이터베이스에서는 하위 클래스인 MyEmployee에 Person이 가지고 있던 2개의 Field를 추가하여 3개의 열을 갖는 테이블을 생성한다.

특징

  • MappedSuperclass 어노테이션을 사용한 클래스는 엔티티가 아니다. 따라서 해당 클래스를 가지고 영속성 컨텍스트 또는 JPQL 등에서 사용할 수 없다.
  • MappedSuperclass 어노테이션을 사용한 클래스는 자체적으로는 의미가 없다. 따라서 추상 클래스로 만드는 것이 좋다.

Single Table 전략

Single Table 전략은 각 클래스 계층에 대해 하나의 테이블을 만든다. 명시적으로 지정하지 않는 경우 JPA 에서는 Default로 해당 전략을 선택한다.

discriminatorType 으로 식별자를 integer로 할 것인지 혹은 다른 타입으로 지정할 것인지 선택할 수 있다. name을 지정하게 되면 해당 식별자의 이름이 지정되어 테이블로 들어간다.

Book에서는 color를 갖지 않고 있는데 테이블에는 모든 필드가 포함되어 있는 모습이다. 만약 Book 데이터가 저장되게 되면 해당 열에는 null 값이 저장된다.

@Entity(name="products")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) // 상속 관계 매핑
@DiscriminatorColumn(name="product_type", 
  discriminatorType = DiscriminatorType.INTEGER)
public class MyProduct {
    @Id
    private long productId;
    private String name;
}
@Entity
@DiscriminatorValue("1")
public class Book extends MyProduct {
    private String author;
}
@Entity
@DiscriminatorValue("2")
public class Pen extends MyProduct {
    private String color;
}

장점

  • 싱글 테이블이기 때문에 조인 쿼리를 사용하지 않고 한 번에 조회가 가능하다.

단점

  • 앞서 확인했던 null 값이 들어가는 자료가 생긴다.
  • 단일 테이블로 유지하기 때문에 테이블의 크기가 커진다.

Joined Table 전략

조인 전략은 계층 구조를 띄는 각 클래스가 해당 테이블로 매핑된다. 이 때, 테이블마다 반복되는 열이 하나가 있는 데, 이는 조인할 때 반드시 필요한 식별자이다.

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Animal {
    @Id
    private long animalId;
    private String species;
}
@Entity
public class Pet extends Animal {
    private String name;
}

두 테이블 모두 animalId 식별자를 갖는다. Pet은 기본 키에 상위 엔티티의 기본키에 대한 외래 키 제약 조건을 갖는다.

만약 해당열을 사용자 지정하려면 @PrimaryKeyJoinColumn 어노테이션을 추가하여 사용할 수 있다.

@Entity
@PrimaryKeyJoinColumn(name = "petId")
public class Pet extends Animal {
    private String name;
}

장점

  • 테이블을 정규화 할 수 있다.
  • 저장공간을 효율적으로 사용한다. → 정규화를 실시했기 때문

단점

  • 조회할 때 외래키를 이용하여 조인 쿼리를 사용하기 때문에 성능이 좋지 않다.
  • 데이터 등록 시 쿼리가 한 번 더 발생한다.

Table per Class

Table per Class 는 각각의 테이블을 모두 생성하는 전략이다. 결과적으로는 MappedSuperclass를 사용한 것과 유사하다. 그러나 Table per Class 전략에서는 상위 클래스에 대한 엔티티를 정의하기 때문에 ( 클래스 자체로 엔티티임 ) 상위 클래스를 연결 및 쿼리 사용이 가능하다.

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Vehicle {
    @Id
    private long vehicleId;

    private String manufacturer;
}

자식 클래스는 위의 코드와 거의 유사하다.

장점

  • 서브 타입과 구분해서 처리할 때 효과적이다.

단점

  • 상속 없이 각 엔티티를 매핑하는 것과 다르지 않아, Union을 사용하여 쿼리를 작성한다.
  • Union 쿼리로 인해 성능이 저하될 수 있다.

레퍼런스
Hibernate Inheritance Mapping

profile
달리기 시작한 치타

0개의 댓글