Hibernate 상속 매핑

1. 개요

관계형 데이터베이스에는 클래스 계층 구조를 데이터베이스 테이블에 매핑하는 간단한 방법이 없습니다.

이를 해결하기 위해 JPA 사양은 몇 가지 전략을 제공합니다.

  • MappedSuperclass – 상위 클래스, 엔티티 일 수 없음
  • 단일 테이블 – 공통 조상을 가진 여러 클래스의 엔티티가 단일 테이블에 배치됩니다.
  • 조인 된 테이블 – 각 클래스에는 테이블이 있으며 하위 클래스 엔터티를 쿼리하려면 테이블을 조인해야합니다.
  • 클래스 별 테이블 – 클래스의 모든 속성이 해당 테이블에 있으므로 조인이 필요하지 않습니다.

각 전략은 다른 데이터베이스 구조를 생성합니다.

엔터티 상속은 슈퍼 클래스를 쿼리 할 때 모든 하위 클래스 엔터티를 검색하기 위해 다형성 쿼리를 사용할 수 있음을 의미합니다.

Hibernate는 JPA 구현이므로, 상속과 관련된 몇 가지 Hibernate 특정 기능뿐만 아니라 위의 모든 것을 포함합니다.

다음 섹션에서는 사용 가능한 전략에 대해 더 자세히 살펴 보겠습니다.

2. MappedSuperclass

MappedSuperclass 전략을 사용하면 상속은 클래스에서만 분명하지만 엔티티 모델에서는 분명하지 않습니다.

부모 클래스를 나타내는 Person 클래스를 만들어 보겠습니다 .

@MappedSuperclass public class Person { @Id private long personId; private String name; // constructor, getters, setters }

이 클래스에는 더 이상 @Entity 주석 이 없습니다. 데이터베이스 자체에서 유지되지 않기 때문입니다.

다음으로 Employee 하위 클래스를 추가해 보겠습니다 .

@Entity public class MyEmployee extends Person { private String company; // constructor, getters, setters }

데이터베이스에서 이것은 하위 클래스의 선언 및 상속 된 필드에 대한 세 개의 열이있는 하나의 "MyEmployee" 테이블에 해당합니다 .

이 전략을 사용하는 경우 조상은 다른 엔티티와의 연결을 포함 할 수 없습니다.

3. 단일 테이블

단일 테이블 전략은 각 클래스 계층에 대해 하나의 테이블을 생성합니다. 이것은 또한 명시 적으로 지정하지 않은 경우 JPA에서 선택한 기본 전략입니다.

@Inheritance 주석을 수퍼 클래스 에 추가하여 사용할 전략을 정의 할 수 있습니다 .

@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) public class MyProduct { @Id private long productId; private String name; // constructor, getters, setters }

엔티티의 식별자는 수퍼 클래스에서도 정의됩니다.

그런 다음 하위 클래스 엔티티를 추가 할 수 있습니다.

@Entity public class Book extends MyProduct { private String author; }
@Entity public class Pen extends MyProduct { private String color; }

3.1. 판별 자 값

모든 엔티티에 대한 레코드가 동일한 테이블에 있으므로 Hibernate는 이들을 구별 할 방법이 필요합니다.

기본적으로 이것은 엔티티 이름을 값으로 갖는 DTYPE 이라는 판별 자 열을 통해 수행됩니다 .

판별 자 열을 사용자 정의하기 위해 @DiscriminatorColumn 주석을 사용할 수 있습니다 .

@Entity(name="products") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name="product_type", discriminatorType = DiscriminatorType.INTEGER) public class MyProduct { // ... }

여기서는 product_type 이라는 정수 열로 MyProduct 하위 클래스 엔터티 를 구분하도록 선택했습니다 .

다음으로, 우리는 각 하위 클래스 레코드가 product_type 열에 대해 가질 값을 Hibernate에게 알려줄 필요가 있습니다 .

@Entity @DiscriminatorValue("1") public class Book extends MyProduct { // ... }
@Entity @DiscriminatorValue("2") public class Pen extends MyProduct { // ... }

Hibernate는 주석이 취할 수있는 두 개의 다른 미리 정의 된 값을 추가합니다 : " null "및 " not null ":

  • @DiscriminatorValue ( "null") – 판별 자 값이없는 모든 행이이 주석을 사용하여 엔티티 클래스에 매핑됨을 의미합니다. 이것은 계층의 루트 클래스에 적용될 수 있습니다.
  • @DiscriminatorValue ( "not null") – 엔티티 정의와 관련된 것과 일치하지 않는 판별 자 값이있는 모든 행은이 주석이있는 클래스에 매핑됩니다.

열 대신 Hibernate 고유의 @DiscriminatorFormula 주석을 사용하여 차별화 값을 결정할 수도 있습니다.

@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorFormula("case when author is not null then 1 else 2 end") public class MyProduct { ... }

이 전략은 상위 항목을 쿼리 할 때 하나의 테이블 만 액세스하면되므로 다형성 쿼리 성능의 이점이 있습니다. 다른 한편, 이것은 또한 우리가 더 이상 하위 클래스 엔티티 속성 NOT NULL 제약 조건을 사용할 수 없음을 의미합니다 .

4. 결합 된 테이블

이 전략을 사용하면 계층 구조의 각 클래스가 해당 테이블에 매핑됩니다. 모든 테이블에 반복적으로 나타나는 유일한 열은 식별자이며 필요할 때 조인하는 데 사용됩니다.

이 전략을 사용하는 수퍼 클래스를 만들어 보겠습니다.

@Entity @Inheritance(strategy = InheritanceType.JOINED) public class Animal { @Id private long animalId; private String species; // constructor, getters, setters }

그런 다음 간단히 하위 클래스를 정의 할 수 있습니다.

@Entity public class Pet extends Animal { private String name; // constructor, getters, setters }

두 테이블 모두 animalId 식별자 열이 있습니다. 의 기본 키 애완 동물 개체는 부모 엔티티의 기본 키 외래 키 제약 조건이 있습니다. 이 열을 사용자 지정하기 위해 @PrimaryKeyJoinColumn 주석을 추가 할 수 있습니다 .

@Entity @PrimaryKeyJoinColumn(name = "petId") public class Pet extends Animal { // ... }

이 상속 매핑 방법의 단점은 엔터티를 검색하려면 테이블 간의 조인이 필요하므로 많은 수의 레코드에 대해 성능이 저하 될 수 있다는 것입니다.

조인의 수는 상위 클래스를 쿼리 할 때 모든 관련 하위와 조인되므로 더 높습니다. 따라서 레코드를 검색하려는 계층이 높을수록 성능이 영향을받을 가능성이 더 큽니다.

5. 클래스 별 테이블

Table Per Class 전략은 상속 된 항목을 포함하여 항목의 모든 속성을 포함하는 테이블에 각 항목을 매핑합니다.

결과 스키마는 @MappedSuperclass를 사용하는 스키마와 비슷 하지만 클래스 당 테이블은 실제로 부모 클래스에 대한 엔터티를 정의하여 결과적으로 연결 및 다형성 쿼리를 허용합니다.

이 전략을 사용하려면 기본 클래스에 @Inheritance 주석 만 추가하면 됩니다.

@Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public class Vehicle { @Id private long vehicleId; private String manufacturer; // standard constructor, getters, setters }

그런 다음 표준 방식으로 하위 클래스를 만들 수 있습니다.

이것은 단순히 상속없이 각 엔티티를 매핑하는 것과 크게 다르지 않습니다. 백그라운드에서 UNION 문 을 사용하여 모든 하위 클래스 레코드를 반환하는 기본 클래스를 쿼리 할 때 구별이 분명 합니다.

The use of UNION can also lead to inferior performance when choosing this strategy. Another issue is that we can no longer use identity key generation.

6. Polymorphic Queries

As mentioned, querying a base class will retrieve all the sub-class entities as well.

Let's see this behavior in action with a JUnit test:

@Test public void givenSubclasses_whenQuerySuperclass_thenOk() { Book book = new Book(1, "1984", "George Orwell"); session.save(book); Pen pen = new Pen(2, "my pen", "blue"); session.save(pen); assertThat(session.createQuery("from MyProduct") .getResultList()).hasSize(2); }

In this example, we've created two Book and Pen objects, then queried their super-class MyProduct to verify that we'll retrieve two objects.

Hibernate can also query interfaces or base classes which are not entities but are extended or implemented by entity classes. Let's see a JUnit test using our @MappedSuperclass example:

@Test public void givenSubclasses_whenQueryMappedSuperclass_thenOk() { MyEmployee emp = new MyEmployee(1, "john", "baeldung"); session.save(emp); assertThat(session.createQuery( "from com.baeldung.hibernate.pojo.inheritance.Person") .getResultList()) .hasSize(1); }

이것은 @MappedSuperclass 이든 아니든 상관없이 모든 수퍼 클래스 또는 인터페이스에서도 작동합니다 . 일반적인 HQL 쿼리와 다른 점은 Hibernate가 관리하는 엔티티가 아니기 때문에 정규화 된 이름을 사용해야한다는 것입니다.

이 유형의 쿼리에 의해 하위 클래스가 반환되는 것을 원하지 않는 경우 EXPLICIT 유형을 사용 하여 Hibernate @Polymorphism 주석을 정의 에 추가하기 만하면 됩니다 .

@Entity @Polymorphism(type = PolymorphismType.EXPLICIT) public class Bag implements Item { ...}

를 쿼리 할 때이 경우, 항목, 가방 레코드가 반환되지 않습니다.

7. 결론

이 기사에서 우리는 Hibernate에서 상속을 매핑하기위한 다양한 전략을 보여 주었다.

예제의 전체 소스 코드는 GitHub에서 찾을 수 있습니다.