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
}
  • このクラスは、それ自体ではデータベースに永続化されないため、 @ Entity アノテーション** がもうないことに注意してください。

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

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

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

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

3シングルテーブル

  • シングルテーブル戦略は、クラス階層ごとに1つのテーブルを作成します** 明示的に指定しない場合は、これも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 {
   //...
}

ここでは、 MyProduct サブクラスエンティティを product typeという integer 列で区別することを選択しました。

次に、各サブクラスのレコードが 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 { ... }
  • この方法では、親エンティティをクエリするときに1つのテーブルにアクセスするだけでよいため、多相クエリのパフォーマンスが向上します 一方、 サブクラスのエンティティプロパティに対して 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 識別子列があります。 Pet エンティティの主キーには、その親エンティティの主キーに対する外部キー制約もあります。この列をカスタマイズするために、 @ 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 ステートメントを使用してすべてのサブクラスのレコードを返す基本クラスを照会すると、違いは明らかです。

  • この戦略を選択するときに UNION を使用すると、パフォーマンスが低下する可能性もあります。

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", "baeldung");
    session.save(emp);

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

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

この種類のクエリでサブクラスを返さないようにする場合は、Hibernate @ Polymorphism アノテーションを EXPLICIT 型の定義に追加するだけです。

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

この場合、 Itemをクエリするときに、 Bag__レコードは返されません。

7. 結論

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

例の完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/hibernate5[GitHubに載っています]。