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に載っています]。