1前書き

この記事では、

@ Formula



@ Where



@ Filter

、および

@ Any

のアノテーションを使用して、Hibernateの動的マッピング機能について説明します。

HibernateはJPA仕様を実装していますが、ここで説明されているアノテーションはHibernateでのみ利用可能で、他のJPA実装に直接移植することはできません。


2プロジェクト設定

機能を実証するには、Hibernate-coreライブラリとバッキングH2データベースだけが必要です。

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.2.12.Final</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.194</version>
</dependency>


hibernate-core

ライブラリの現在のバージョンについては、https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.hibernate%22%20AND%20a%3A%22hibernateに進んでください-core%22[メイヴン中央]。


3

@ Formula


を使用して計算列

他のプロパティに基づいてエンティティフィールドの値を計算したいとします。そのための1つの方法は、私たちのJavaエンティティで計算された読み取り専用フィールドを定義することです。

@Entity
public class Employee implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private long grossIncome;

    private int taxInPercents;

    public long getTaxJavaWay() {
        return grossIncome **  taxInPercents/100;
    }

}

明らかな欠点は、

ゲッターによってこの仮想フィールドにアクセスするたびに

再計算しなければならないことです。

データベースからすでに計算済みの値を取得する方がはるかに簡単です。これは

@ Formula

アノテーションを使って行うことができます。

@Entity
public class Employee implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private long grossIncome;

    private int taxInPercents;

    @Formula("grossIncome **  taxInPercents/100")
    private long tax;

}


  • @ Formula

    を使用すると、サブクエリを使用したり、ネイティブデータベース関数やストアドプロシージャを呼び出したり、基本的にこのフィールドのSQL select句の構文を壊さないようなことはすべて実行できます。

Hibernateは提供したSQLを解析して正しいテーブルとフィールドのエイリアスを挿入するのに十分スマートです。注意すべき注意点は、注釈の値は生のSQLであるため、マッピングがデータベースに依存する可能性があることです。

また、値はエンティティがデータベースから取得されたときに計算されることにも注意してください。したがって、エンティティを永続化または更新しても、エンティティがコンテキストから削除されて再びロードされるまで、値は再計算されません。

Employee employee = new Employee(10__000L, 25);
session.save(employee);

session.flush();
session.clear();

employee = session.get(Employee.class, employee.getId());
assertThat(employee.getTax()).isEqualTo(2__500L);


4

@ Where


を使用したエンティティのフィルタリング

エンティティを要求するたびにクエリに追加の条件を付けたいとします。

たとえば、「ソフト削除」を実装する必要があります。つまり、エンティティはデータベースから削除されることはなく、

boolean

フィールドで削除済みとしてマークされるだけです。

アプリケーション内の既存および将来のすべてのクエリには細心の注意を払う必要があります。この追加条件をすべてのクエリに提供する必要があります。幸い、Hibernateはこれを一箇所で行う方法を提供します。

@Entity
@Where(clause = "deleted = false")
public class Employee implements Serializable {

   //...
}

メソッドの

@ Where

アノテーションには、このエンティティに対するクエリまたはサブクエリに追加されるSQL句が含まれています。

employee.setDeleted(true);

session.flush();
session.clear();

employee = session.find(Employee.class, employee.getId());
assertThat(employee).isNull();


@ Formula

アノテーションの場合と同様に、

生のSQLを扱っているので、エンティティをデータベースにフラッシュしてコンテキストから削除するまで

@ Where

条件は再評価されません

それまでは、エンティティはコンテキスト内に留まり、クエリと

id

による検索でアクセス可能になります。


@ Where

注釈は、コレクションフィールドにも使用できます。削除可能な電話のリストがあるとします。

@Entity
public class Phone implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private boolean deleted;

    private String number;

}

次に、

Employee

側から、削除可能な

phones

のコレクションを次のようにマッピングできます。

public class Employee implements Serializable {

   //...

    @OneToMany
    @JoinColumn(name = "employee__id")
    @Where(clause = "deleted = false")
    private Set<Phone> phones = new HashSet<>(0);

}

違いは、

Employee.phones

コレクションは常にフィルタリングされることですが、直接クエリを使用して、削除されたものも含め、すべての電話を取得できるということです。

employee.getPhones().iterator().next().setDeleted(true);
session.flush();
session.clear();

employee = session.find(Employee.class, employee.getId());
assertThat(employee.getPhones()).hasSize(1);

List<Phone> fullPhoneList
  = session.createQuery("from Phone").getResultList();
assertThat(fullPhoneList).hasSize(2);


5

@ Filter


によるパラメータ化フィルタリング


@ Where

アノテーションの問題点は、パラメータなしで静的クエリを指定することしかできず、必要に応じて無効化または有効化できないことです。


@ Filter

アノテーションは

@ Where

と同じように機能しますが、セッションレベルで有効または無効にしたり、パラメータ化することもできます。


5.1.

@ Filter


を定義する


@ Filter

のしくみを説明するために、まず

Employee

エンティティに次のフィルタ定義を追加しましょう。

@FilterDef(
    name = "incomeLevelFilter",
    parameters = @ParamDef(name = "incomeLimit", type = "int")
)
@Filter(
    name = "incomeLevelFilter",
    condition = "grossIncome > :incomeLimit"
)
public class Employee implements Serializable {


@ FilterDef

アノテーションは、クエリに参加するフィルタ名とそのパラメータのセットを定義します。パラメータの型は、Hibernate型の1つの名前です(https://docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/type/Type.html[Type]、https://docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/usertype/UserType.html[UserType]またはhttps://docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/usertype/CompositeUserType.html[CompositeUserType])、この場合は

int

です。

@FilterDef

アノテーションは、タイプまたはパッケージレベルのどちらにも配置できます。フィルタ条件自体は指定されていません(ただし、

defaultCondition__パラメータを指定できます)。

つまり、フィルタ(その名前とパラメータのセット)を1か所で定義してから、フィルタの条件を他の複数の場所で別々に定義できるということです。

これは

@ Filter

アノテーションを使って行うことができます。私たちの場合は、単純にするために同じクラスに入れています。条件の構文は、コロンが前に付いたパラメータ名を持つ生のSQLです。


5.2. フィルタリングされたエンティティへのアクセス


@からの

@ Filter

のもう1つの違いは、

@ Filter__はデフォルトでは有効になっていないことです。セッションレベルでそれを手動で有効にし、それにパラメータ値を提供する必要があります。

session.enableFilter("incomeLevelFilter")
  .setParameter("incomeLimit", 11__000);

データベースに次の3人の従業員がいるとします。

session.save(new Employee(10__000, 25));
session.save(new Employee(12__000, 25));
session.save(new Employee(15__000, 25));

次に、上記のようにフィルタを有効にした状態で、クエリを実行すると表示されるのは2つだけです。

List<Employee> employees = session.createQuery("from Employee")
  .getResultList();
assertThat(employees).hasSize(2);

有効フィルタとそのパラメータ値の両方が現在のセッション内でのみ適用されることに注意してください。フィルタが有効になっていない新しいセッションでは、3人の従業員全員が表示されます。

session = HibernateUtil.getSessionFactory().openSession();
employees = session.createQuery("from Employee").getResultList();
assertThat(employees).hasSize(3);

また、idによってエンティティを直接取得する場合、フィルタは適用されません。

Employee employee = session.get(Employee.class, 1);
assertThat(employee.getGrossIncome()).isEqualTo(10__000);


5.3.

@フィルタ

と第2レベルのキャッシング

高負荷のアプリケーションを使用している場合は、Hibernateの2次キャッシュを必ず有効にすることをお勧めします。これはパフォーマンス上の大きなメリットになります。


@ Filter

アノテーションはキャッシュに対してうまく機能しないことに注意してください。

  • 2次キャッシュは、フィルタ処理されていないフルコレクションのみを保持します。そうでない場合は、フィルタを有効にして1つのセッションでコレクションを読み取り、フィルタを無効にしても別のセッションで同じキャッシュ済みフィルタコレクションを取得できます。

これが

@ Filter

アノテーションが基本的にエンティティのキ​​ャッシュを無効にする理由です。


6.

@ Any


を使用したエンティティ参照のマッピング

単一の

@ MappedSuperclass

に基づいていなくても、参照を複数のエンティティタイプのいずれかにマップしたい場合があります。それらは、異なる無関係のテーブルにもマップできます。これを

@ Any

アノテーションで実現できます。

私たちの例では、

私たちのパーシスタンスユニット

のすべてのエンティティ、つまり

Employee



Phone

に何らかの説明を付ける必要があります。これを行うためだけにすべてのエンティティを単一の抽象スーパークラスから継承するのは不合理です。


6.1.

@ Any


とのマッピング関係

これは、

Serializable

を実装するエンティティへの参照(つまり、すべてのエンティティへの参照)を定義する方法です。

@Entity
public class EntityDescription implements Serializable {

    private String description;

    @Any(
        metaDef = "EntityDescriptionMetaDef",
        metaColumn = @Column(name = "entity__type"))
    @JoinColumn(name = "entity__id")
    private Serializable entity;

}


metaDef

プロパティは定義の名前、

metaColumn

はエンティティタイプを区別するために使用される列の名前です(単一テーブル階層マッピングの識別子列とは異なります)。

エンティティの

id

を参照する列も指定します。

  • この列は外部キーにはならないことに注意してください。** これは必要なテーブルを参照できるためです。


entity

id__列も、一意ではありません。テーブルごとに異なる識別子が繰り返される可能性があるためです。


  • entity

    type

    /

    entity

    id

    ペアは、参照しているエンティティを一意に表すため、一意である必要があります。**


6.2.

@ AnyMetaDef


を使用した

@ Any

マッピングの定義

現時点では、Hibernateは

entity

type__列に含めることができるものを指定していないため、さまざまなエンティティタイプを区別する方法を知りません。

これを機能させるには、

@ AnyMetaDef

アノテーションを使用してマッピングのメタ定義を追加する必要があります。それを置くのに最適な場所はパッケージレベルですので、他のマッピングで再利用することができます。

これは、

@ AnyMetaDef

アノテーションを含む

package-info.java

ファイルがどのようになるかです。

@AnyMetaDef(
    name = "EntityDescriptionMetaDef",
    metaType = "string",
    idType = "int",
    metaValues = {
        @MetaValue(value = "Employee", targetEntity = Employee.class),
        @MetaValue(value = "Phone", targetEntity = Phone.class)
    }
)
package com.baeldung.hibernate.pojo;

ここでは、

entity

type

列の型(

string

)、

entity

id

列の型(

int

)、

entity

type

列の許容値(

“Employee”

および

“Phone” __)、および対応するエンティティ型を指定しました。

今、2台の電話を持つ従業員がこのように記述されているとします。

Employee employee = new Employee();
Phone phone1 = new Phone("555-45-67");
Phone phone2 = new Phone("555-89-01");
employee.getPhones().add(phone1);
employee.getPhones().add(phone2);

無関係なタイプが異なっていても、3つすべてのエンティティに記述メタデータを追加できます。

EntityDescription employeeDescription = new EntityDescription(
  "Send to conference next year", employee);
EntityDescription phone1Description = new EntityDescription(
  "Home phone (do not call after 10PM)", phone1);
EntityDescription phone2Description = new EntityDescription(
  "Work phone", phone1);


7. 結論

この記事では、生のSQLを使用してエンティティマッピングを微調整できるようにするHibernateのアノテーションのいくつかを調べました。

この記事のソースコードはhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/hibernate5[GitHubで利用可能]です。