Hibernateの継承マッピング

Hibernate継承マッピング

1. 概要

リレーショナルデータベースには、クラス階層をデータベーステーブルにマッピングする簡単な方法がありません。

これに対処するため、JPA仕様にはいくつかの戦略があります。

  • MappedSuperclass –親クラス。エンティティにすることはできません

  • 単一のテーブル-共通の祖先を持つ異なるクラスのエンティティが単一のテーブルに配置されます

  • 結合テーブル-各クラスにはテーブルがあり、サブクラスエンティティのクエリにはテーブルの結合が必要です

  • Table-Per-Class-クラスのすべてのプロパティはそのテーブルにあるため、結合は不要です

各戦略は、異なるデータベース構造をもたらします。

エンティティの継承とは、スーパークラスをクエリするときに、ポリモーフィッククエリを使用してすべてのサブクラスエンティティを取得できることを意味します。

HibernateはJPA実装であるため、上記のすべてと、継承に関連するいくつかのHibernate固有の機能が含まれています。

次のセクションでは、利用可能な戦略について詳しく説明します。

2. MappedSuperclass

MappedSuperclass戦略を使用すると、継承はクラスでのみ明らかになり、エンティティモデルでは明らかになりません。

親クラスを表すPersonクラスを作成することから始めましょう。

@MappedSuperclass
public class Person {

    @Id
    private long personId;
    private String name;

    // constructor, getters, setters
}

Notice that this class no longer has an @Entity annotation、それ自体はデータベースに保持されないため。

次に、Employeeサブクラスを追加しましょう。

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

データベースでは、これは、サブクラスの宣言されたフィールドと継承されたフィールドの3つの列を持つ1つの“MyEmployee”テーブルに対応します。

この戦略を使用している場合、祖先に他のエンティティとの関連付けを含めることはできません。

3. シングルテーブル

The Single Table strategy creates one table for each class hierarchy.これは、明示的に指定しない場合に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 needs a way to differentiate between them.

エンティティの名前を値として持つBy default, this is done through a discriminator column called DTYPE

識別子列をカスタマイズするには、@DiscriminatorColumnアノテーションを使用できます。

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

ここでは、product_type.と呼ばれるinteger列によってMyProductサブクラスエンティティを区別することを選択しました

次に、各サブクラスレコードがproduct_type列に対してどのような値を持つかをHibernateに指示する必要があります。

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

Hibernateは、注釈が取ることができる他の2つの事前定義された値「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 { ... }

This strategy has the advantage of polymorphic query performance since only one table needs to be accessed when querying parent entities.一方、これはwe can no longer use NOT NULL constraints on sub-classエンティティプロパティも意味します。

4. 参加テーブル

Using this strategy, each class in the hierarchy is mapped to its table.すべてのテーブルに繰り返し表示される唯一の列は識別子であり、必要に応じてそれらを結合するために使用されます。

この戦略を使用するスーパークラスを作成しましょう。

@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識別子列があります。 Petエンティティの主キーには、その親エンティティの主キーに対する外部キー制約もあります。 この列をカスタマイズするために、@PrimaryKeyJoinColumnアノテーションを追加できます。

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

The disadvantage of this inheritance mapping method is that retrieving entities requires joins between tables。これにより、多数のレコードのパフォーマンスが低下する可能性があります。

関連するすべての子と結合するため、親クラスを照会する場合、結合の数は多くなります。したがって、レコードを取得する階層の上位でパフォーマンスが影響を受ける可能性が高くなります。

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.もう1つの問題は、IDキーの生成を使用できなくなったことです。

6. ポリモーフィッククエリ

前述のように、基本クラスを照会すると、すべてのサブクラスエンティティも取得されます。

JUnitテストでこの動作を実際に見てみましょう。

@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);
}

この例では、2つのBookオブジェクトとPenオブジェクトを作成し、それらのスーパークラスMyProductにクエリを実行して、2つのオブジェクトを取得することを確認しました。

Hibernateは、エンティティーではないが、エンティティークラスによって拡張または実装されるインターフェースまたは基本クラスを照会することもできます。 @MappedSuperclassの例を使用してJUnitテストを見てみましょう。

@Test
public void givenSubclasses_whenQueryMappedSuperclass_thenOk() {
    MyEmployee emp = new MyEmployee(1, "john", "example");
    session.save(emp);

    assertThat(session.createQuery(
      "from com.example.hibernate.pojo.inheritance.Person")
      .getResultList())
      .hasSize(1);
}

これは、@MappedSuperclassであるかどうかに関係なく、すべてのスーパークラスまたはインターフェイスでも機能することに注意してください。 通常のHQLクエリとの違いは、Hibernateが管理するエンティティではないため、完全修飾名を使用する必要があることです。

このタイプのクエリによってサブクラスが返されることを望まない場合は、タイプEXPLICITを使用してHibernate@Polymorphismアノテーションをその定義に追加するだけで済みます。

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

この場合、Items,を照会すると、Bagレコードは返されません。

7. 結論

この記事では、Hibernateで継承をマッピングするためのさまざまな戦略を示しました。

例の完全なソースコードはover on GitHubにあります。