Hibernate 2次キャッシュ

Hibernate 2次キャッシュ

1. 概要

ORM(オブジェクトリレーショナルマッピング)フレームワークなどのデータベース抽象化レイヤーの利点の1つは、基になるストアから取得されるability to transparently cache dataです。 これにより、頻繁にアクセスされるデータのデータベースアクセスコストを削減できます。

キャッシュされたコンテンツの読み取り/書き込み比率が高い場合、特に大きなオブジェクトグラフで構成されるエンティティの場合、パフォーマンスの向上は顕著です。

この記事では、Hibernateの第2レベルのキャッシュについて説明します。

いくつかの基本的な概念を説明し、いつものようにすべてを簡単な例で説明します。 JPAを使用し、JPAで標準化されていない機能に対してのみHibernateネイティブAPIにフォールバックします。

2. 第2レベルのキャッシュとは何ですか?

他のほとんどの完全装備のORMフレームワークと同様に、Hibernateには一次キャッシュの概念があります。 これは、各エンティティインスタンスが永続コンテキストで一度だけロードされることを保証するセッションスコープキャッシュです。

セッションが閉じられると、一次キャッシュも終了します。 これは、同時セッションがエンティティインスタンスを相互に分離して動作できるようにするため、実際に望ましい方法です。

一方、第2レベルのキャッシュはSessionFactoryスコープです。つまり、同じセッションファクトリで作成されたすべてのセッションで共有されます。 エンティティインスタンスがそのIDによって(アプリケーションロジックまたは内部的にHibernateによって、他のエンティティからそのエンティティへの関連付けをロードするときにe.g.)ルックアップされる場合、およびそのエンティティに対して第2レベルのキャッシュが有効になっている場合、次のことが起こります:

  • インスタンスが1次キャッシュに既に存在する場合、そこから返されます

  • インスタンスが1次キャッシュで見つからず、対応するインスタンス状態が2次キャッシュにキャッシュされている場合、そこからデータがフェッチされ、インスタンスがアセンブルされて返されます

  • それ以外の場合、必要なデータがデータベースからロードされ、インスタンスがアセンブルされて返されます

インスタンスが永続コンテキスト(1次キャッシュ)に格納されると、セッションが閉じられるか、インスタンスが永続コンテキストから手動で削除されるまで、同じセッション内の後続のすべての呼び出しでそこから返されます。 また、ロードされたインスタンスの状態は、まだ存在していない場合、L2キャッシュに保存されます。

3. リージョンファクトリー

Hibernateの2次キャッシュは、実際に使用されるキャッシュプロバイダーを認識しないように設計されています。 Hibernateには、実際のキャッシュプロバイダーに固有のすべての詳細をカプセル化するorg.hibernate.cache.spi.RegionFactoryインターフェイスの実装のみを提供する必要があります。 基本的に、Hibernateとキャッシュプロバイダー間のブリッジとして機能します。

この記事では、成熟した広く使用されているキャッシュであるwe use Ehcache as a cache providerを紹介します。 もちろん、RegionFactoryが実装されている限り、他のプロバイダーを選択できます。

次のMaven依存関係を使用して、Ehcacheリージョンファクトリの実装をクラスパスに追加します。


    org.hibernate
    hibernate-ehcache
    5.2.2.Final

hibernate-ehcacheの最新バージョンの場合はTake a look here。 ただし、hibernate-ehcacheバージョンがプロジェクトで使用するHibernateバージョンと等しいことを確認してください。この例のようにhibernate-ehcache 5.2.2.Finalを使用する場合は、e.g.も%である必要があります。 (t5)s。

hibernate-ehcacheアーティファクトは、Ehcache実装自体に依存しているため、クラスパスにも推移的に含まれます。

4. 第2レベルのキャッシュを有効にする

次の2つのプロパティを使用して、L2キャッシングが有効になっていることをHibernateに伝え、リージョンファクトリクラスの名前を付けます。

hibernate.cache.use_second_level_cache=true
hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory

たとえば、persistence.xmlでは次のようになります。


    ...
    
    
    ...

第2レベルのキャッシュを無効にするには(たとえば、デバッグ目的で)、hibernate.cache.use_second_level_cacheプロパティをfalseに設定するだけです。

5. エンティティをキャッシュ可能にする

make an entity eligible for second-level cachingにするために、Hibernate固有の@org.hibernate.annotations.Cacheアノテーションを付け、cache concurrency strategyを指定します。

一部の開発者は、標準の@javax.persistence.Cacheableアノテーションも追加することをお勧めします(ただし、Hibernateでは必須ではありません)。したがって、エンティティークラスの実装は次のようになります。

@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Foo {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID")
    private long id;

    @Column(name = "NAME")
    private String name;

    // getters and setters
}

エンティティクラスごとに、Hibernateは個別のキャッシュ領域を使用して、そのクラスのインスタンスの状態を保存します。 領域名は完全修飾クラス名です。

たとえば、Fooインスタンスは、Ehcacheのcom.example.hibernate.cache.model.Fooという名前のキャッシュに保存されます。

キャッシングが機能していることを確認するために、次のような簡単なテストを作成できます。

Foo foo = new Foo();
fooService.create(foo);
fooService.findOne(foo.getId());
int size = CacheManager.ALL_CACHE_MANAGERS.get(0)
  .getCache("com.example.hibernate.cache.model.Foo").getSize();
assertThat(size, greaterThan(0));

ここでは、Ehcache APIを直接使用して、Fooインスタンスをロードした後にcom.example.hibernate.cache.model.Fooキャッシュが空でないことを確認します。

Hibernateによって生成されたSQLのロギングを有効にし、テストでfooService.findOne(foo.getId())を複数回呼び出して、Fooをロードするためのselectステートメントが1回だけ(初回)出力されることを確認することもできます。以降の呼び出しでは、エンティティインスタンスがキャッシュからフェッチされます。

6. キャッシュ同時実行戦略

ユースケースに基づいて、次のキャッシュ並行性戦略のいずれかを自由に選択できます。

  • READ_ONLY:変更されないエンティティにのみ使用されます(そのようなエンティティを更新しようとすると例外がスローされます)。 非常にシンプルで高性能です。 変更されない一部の静的参照データに非常に適しています

  • NONSTRICT_READ_WRITE:影響を受けるデータを変更したトランザクションがコミットされた後、キャッシュが更新されます。 したがって、強力な一貫性は保証されず、キャッシュから古いデータが取得される可能性のある小さな時間枠があります。 この種の戦略は、結果整合性を許容できるユースケースに適しています

  • READ_WRITE:この戦略は、「ソフト」ロックを使用して達成する強力な一貫性を保証します。キャッシュされたエンティティが更新されると、ソフトロックもそのエンティティのキ​​ャッシュに保存され、トランザクションがコミットされた後に解放されます。 。 ソフトロックされたエントリにアクセスするすべての同時トランザクションは、データベースから対応するデータを直接フェッチします

  • TRANSACTIONAL:キャッシュの変更は分散XAトランザクションで行われます。 キャッシュされたエンティティの変更は、同じXAトランザクションのデータベースとキャッシュの両方でコミットまたはロールバックされます。

7. キャッシュ管理

有効期限とエビクションのポリシーが定義されていない場合、キャッシュは無期限に増大し、最終的に利用可能なメモリをすべて消費する可能性があります。 ほとんどの場合、Hibernateはこれらのようなキャッシュ管理義務をキャッシュプロバイダーに任せます。これらは実際には各キャッシュ実装に固有のものです。

たとえば、次のEhcache構成を定義して、キャッシュされるFooインスタンスの最大数を1000に制限できます。


    

8. コレクションキャッシュ

コレクションはデフォルトではキャッシュされないため、キャッシュ可能として明示的にマークする必要があります。 例えば:

@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Foo {

    ...

    @Cacheable
    @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    @OneToMany
    private Collection bars;

    // getters and setters
}

9. キャッシュされた状態の内部表現

エンティティは、Javaインスタンスとして2次キャッシュに保存されるのではなく、逆アセンブル(水和)状態で保存されます。

  • Id(主キー)は保存されません(キャッシュキーの一部として保存されます)

  • 一時的なプロパティは保存されません

  • コレクションは保存されません(詳細については以下を参照)

  • 非関連付けプロパティ値は元の形式で保存されます

  • ToOneの関連付けには、id(外部キー)のみが保存されます

これは、キャッシュモデルが基礎となるリレーショナルモデルを反映する一般的なHibernateの2次キャッシュデザインを示しています。これは、スペース効率がよく、2つの同期を簡単に保つことができます。

9.1. キャッシュされたコレクションの内部表現

コレクション(OneToManyまたはManyToManyアソシエーション)がキャッシュ可能であることを明示的に示す必要があることはすでに説明しました。そうでない場合、キャッシュされません。

実際、Hibernateはコレクションを、コレクションごとに1つずつ、別々のキャッシュ領域に保存します。 リージョン名は、完全修飾クラス名とコレクションプロパティの名前です(例:com.example.hibernate.cache.model.Foo.bars)。 これにより、コレクションの個別のキャッシュパラメータ、e.g.のエビクション/有効期限ポリシーを柔軟に定義できます。

また、コレクションに含まれるエンティティのIDのみが各コレクションエントリにキャッシュされることに注意することが重要です。つまり、ほとんどの場合、含まれるエンティティもキャッシュ可能にすることをお勧めします。

10. HQLDMLスタイルのクエリとネイティブクエリのキャッシュの無効化

DMLスタイルのHQL(insertupdate、およびdelete HQLステートメント)に関しては、Hibernateはそのような操作の影響を受けるエンティティを判別できます。

entityManager.createQuery("update Foo set … where …").executeUpdate();

この場合、すべてのFooインスタンスはL2キャッシュから削除されますが、他のキャッシュされたコンテンツは変更されません。

ただし、ネイティブSQL DMLステートメントに関しては、Hibernateは何が更新されているか推測できないため、2次キャッシュ全体を無効にします。

session.createNativeQuery("update FOO set … where …").executeUpdate();

これはおそらくあなたが望むものではありません! 解決策は、ネイティブDMLステートメントの影響を受けるエンティティをHibernateに通知して、Fooエンティティに関連するエントリのみを削除できるようにすることです。

Query nativeQuery = entityManager.createNativeQuery("update FOO set ... where ...");
nativeQuery.unwrap(org.hibernate.SQLQuery.class).addSynchronizedEntityClass(Foo.class);
nativeQuery.executeUpdate();

この機能は(まだ)JPAで定義されていないため、HibernateネイティブSQLQueryAPIにフォールバックしすぎています。

上記はDMLステートメント(insertupdatedeleteおよびネイティブ関数/プロシージャ呼び出し)にのみ適用されることに注意してください。 ネイティブselectクエリはキャッシュを無効にしません。

11. クエリキャッシュ

HQLクエリの結果もキャッシュできます。 これは、めったに変更されないエンティティに対して頻繁にクエリを実行する場合に役立ちます。

クエリキャッシュを有効にするには、hibernate.cache.use_query_cacheプロパティの値をtrueに設定します。

hibernate.cache.use_query_cache=true

次に、クエリごとに、クエリがキャッシュ可能であることを明示的に示す必要があります(org.hibernate.cacheableクエリヒントを使用)。

entityManager.createQuery("select f from Foo f")
  .setHint("org.hibernate.cacheable", true)
  .getResultList();

11.1. クエリキャッシュのベストプラクティス

ここにいくつかのguidelines and best practices related to query cachingがあります:

  • コレクションの場合と同様に、キャッシュ可能なクエリの結果として返されるエンティティのIDのみがキャッシュされるため、そのようなエンティティに対して2次キャッシュを有効にすることを強くお勧めします。

  • 各クエリのクエリパラメーター値(バインド変数)の各組み合わせごとに1つのキャッシュエントリが存在するため、パラメーター値のさまざまな組み合わせが多数あると予想されるクエリは、キャッシュに適していません。

  • データベースに頻繁に変更があるエンティティクラスを含むクエリは、変更されたインスタンスがクエリ結果の一部としてキャッシュされるかどうか。

  • デフォルトでは、すべてのクエリキャッシュの結果はorg.hibernate.cache.internal.StandardQueryCache領域に保存されます。 エンティティ/コレクションキャッシングと同様に、この領域のキャッシュパラメーターをカスタマイズして、必要に応じてエビクションおよび有効期限ポリシーを定義できます。 クエリごとにカスタム領域名を指定して、クエリごとに異なる設定を提供することもできます。

  • キャッシュ可能なクエリの一部としてクエリされるすべてのテーブルについて、Hibernateは最終更新タイムスタンプをorg.hibernate.cache.spi.UpdateTimestampsCacheという名前の別の領域に保持します。 クエリキャッシングを使用する場合、Hibernateはこの領域を使用して、キャッシュされたクエリ結果が古くないことを確認するため、この領域を認識することは非常に重要です。 クエリ結果領域内の対応するテーブルのクエリ結果がキャッシュされている限り、このキャッシュ内のエントリを削除または期限切れにしないでください。 とにかく大量のメモリを消費しないので、このキャッシュ領域の自動追い出しと有効期限をオフにすることをお勧めします。

12. 結論

この記事では、Hibernateの2次キャッシュをセットアップする方法について説明しました。 Hibernateは舞台裏ですべての面倒な作業を行うため、2番目のレベルのキャッシュ使用率がアプリケーションのビジネスロジックに対して透過的になるため、構成と使用が非常に簡単であることがわかりました。

このHibernateの第2レベルのキャッシュチュートリアルの実装はon Githubで利用できます。 これはMavenベースのプロジェクトであるため、インポートしてそのまま実行するのは簡単です。