1概要
ORM(オブジェクトリレーショナルマッピング)フレームワークなどのデータベース抽象化レイヤの利点の1つは、基盤となるストアから取得したデータを透過的にキャッシュする機能** です。これにより、頻繁にアクセスされるデータに対するデータベースアクセスコストを削減できます。
キャッシュされたコンテンツの読み取り/書き込み率が高い場合、特に大きなオブジェクトグラフで構成されているエンティティの場合、パフォーマンスが大幅に向上する可能性があります。
-
この記事ではHibernateの2次キャッシュについて説明します。**
私たちはいくつかの基本的な概念を説明し、いつものように簡単な例ですべてを説明します。 JPAを使用し、JPAで標準化されていない機能についてのみHibernateネイティブAPIに戻ります。
** 2 2次キャッシュとは
他のほとんどの完全装備のORMフレームワークと同様に、Hibernateは一次キャッシュの概念を持っています。これはセッションスコープのキャッシュで、各エンティティインスタンスが永続コンテキスト内で一度だけロードされるようにします。
セッションが閉じられると、1次キャッシュも終了します。
これは、同時セッションが互いに独立してエンティティインスタンスと連携することを可能にするため、実際には望ましいことです。
一方、2次キャッシュは SessionFactory スコープであり、同じセッションファクトリで作成されたすべてのセッションで共有されます。エンティティインスタンスがそのIDによって(アプリケーションロジックまたは内部でHibernateによって、他のエンティティからそのエンティティへの関連付けを読み込むときに)検索され、そのエンティティに対して第2レベルのキャッシュが有効になっている場合、次のことが起こります。
-
インスタンスがすでに第1レベルのキャッシュに存在する場合、それは
そこから戻った ** 一次キャッシュにインスタンスが見つからない場合
対応するインスタンスの状態は2次キャッシュにキャッシュされます。 そこからデータが取り出され、インスタンスが組み立てられて返されます。 ** それ以外の場合、必要なデータはデータベースからロードされ、
インスタンスが組み立てられて返される
インスタンスが永続コンテキスト(第1レベルのキャッシュ)に格納されると、セッションが閉じられるまで、またはインスタンスが永続コンテキストから手動で削除されるまで、同じセッション内の後続のすべての呼び出しでそこから返されます。また、ロードされたインスタンスの状態は、まだ存在していなければ、L2キャッシュに格納されます。
3リージョンファクトリー
Hibernateのセカンドレベルキャッシングは、実際に使用されているキャッシュプロバイダーを意識しないように設計されています。 Hibernateには、実際のキャッシュプロバイダーに固有のすべての詳細をカプセル化する org.hibernate.cache.spi.RegionFactory インターフェースの実装を提供するだけで済みます。
基本的には、Hibernateとキャッシュプロバイダ間のブリッジとして機能します。
この記事では、キャッシュプロバイダーとしてEhcacheを使用します。これは、成熟した広く使用されているキャッシュです。 RegionFactory が実装されていれば、他のプロバイダを選ぶこともできます。
次のMaven依存関係を使用して、Ehcache領域ファクトリ実装をクラスパスに追加します。
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>5.2.2.Final</version>
</dependency>
https://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22org.hibernate%22%20AND%20a%3A%22hibernate-ehcache%22 禁止 - ehcache 。ただし、 hibernate-ehcache のバージョンが、プロジェクトで使用しているHibernateのバージョンと同じであることを確認してください。この例のように hibernate-ehcache 5.2.2.Final を使用している場合は、Hibernateのバージョンも同じになります。 5.2.2。最終
hibernate-ehcache アーティファクトはEhcacheの実装自体に依存しているため、推移的にクラスパスにも含まれる
4.第2レベルキャッシングの有効化
次の2つのプロパティを使って、HibernateにL2キャッシュが有効になっていることを伝え、リージョンファクトリクラスの名前を付けます。
hibernate.cache.use__second__level__cache=true
hibernate.cache.region.factory__class=org.hibernate.cache.ehcache.EhCacheRegionFactory
たとえば、 persistence.xml では、次のようになります。
<properties>
...
<property name="hibernate.cache.use__second__level__cache" value="true"/>
<property name="hibernate.cache.region.factory__class"
value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>
...
</properties>
第2レベルのキャッシュを無効にするには(たとえばデバッグ目的で)、 hibernate.cache.use second level cache__プロパティをfalseに設定するだけです。
5エンティティをキャッシュ可能にする
エンティティをセカンドレベルキャッシングの対象にするには、Hibernate固有の @org.hibernate.annotations.Cache アノテーションを付けて、#cacheConcurrencyStrategy[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の org.baeldung.persistence.model.Foo という名前のキャッシュに格納されます。
キャッシュが機能していることを確認するために、このような簡単なテストを書くことができます。
Foo foo = new Foo();
fooService.create(foo);
fooService.findOne(foo.getId());
int size = CacheManager.ALL__CACHE__MANAGERS.get(0)
.getCache("org.baeldung.persistence.model.Foo").getSize();
assertThat(size, greaterThan(0));
ここでは、Ehcache APIを直接使用して、 Foo インスタンスをロードした後で org.baeldung.persistence.model.Foo キャッシュが空ではないことを確認します。
また、Hibernateによって生成されたSQLのロギングを有効にして、テストで fooService.findOne(foo.getId()) を複数回呼び出すことで、 Foo をロードするための select ステートメントが1回だけ(最初の)印刷されるその後の呼び出しでは、エンティティインスタンスがキャッシュから取得されます。
6. キャッシュ並行性戦略
ユースケースに基づいて、次のいずれかのキャッシュ同時方式を自由に選択できます。
-
READ ONLY__ :決して変更されないエンティティにのみ使用されます(例外は
そのようなエンティティを更新しようとした場合にスローされます。とてもです シンプルで高性能。いくつかの静的参照データに非常に適しています 変わらない NONSTRICT READ WRITE ** :キャッシュはそのトランザクションの後に更新されます。
影響を受けたデータがコミットされたことを変更しました。したがって、強い一貫性 保証されておらず、古いデータが保存されている小さな時間枠があります キャッシュから取得できます。この種の戦略は使用に適しています 最終的な一貫性を許容できるケース READ WRITE__ ** :この戦略は強力な一貫性を保証します。
「ソフト」ロックを使用して実現します。キャッシュされたエンティティが更新されると、ソフトロックが解除されます。 ロックはそのエンティティのキャッシュにも格納され、解放されます。 トランザクションがコミットされた後。すべての同時トランザクション ソフトロックエントリにアクセスすると、対応するデータが直接取得されます。 データベースから TRANSACTIONAL ** :キャッシュの変更は分散XAで行われます
トランザクションキャッシュされたエンティティの変更は、同じXAトランザクション内のデータベースとキャッシュの両方でコミットまたはロールバックされます。
7. キャッシュ管理
有効期限ポリシーおよび削除ポリシーが定義されていない場合、キャッシュは無制限に大きくなり、最終的には使用可能なメモリをすべて消費する可能性があります。ほとんどの場合、Hibernateはこれらのキャッシュ管理業務をキャッシュプロバイダに任せています。これは、実際には各キャッシュ実装に固有のものであるためです。
たとえば、キャッシュされた Foo インスタンスの最大数を1000に制限するために、次のEhcache設定を定義できます。
<ehcache>
<cache name="org.baeldung.persistence.model.Foo" maxElementsInMemory="1000"/>
</ehcache>
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<Bar> bars;
//getters and setters
}
9キャッシュ状態の内部表現
エンティティは、Javaインスタンスとして2次レベルのキャッシュに格納されるのではなく、逆に分解された(水和された)状態で格納されます。
-
Id(主キー)は保存されません(キャッシュキーの一部として保存されます)
-
一時的なプロパティは保存されません
-
コレクションは保存されていません(詳細は下記を参照)
-
非関連プロパティ値は元の形式で保存されます
-
ToToOne__アソシエーションには、ID(外部キー)のみが格納されます。
これは、キャッシュモデルが基礎となるリレーショナルモデルを反映した一般的なHibernateの第2レベルキャッシュ設計を示しています。
9.1. キャッシュコレクションの内部表現
コレクション( OneToMany または ManyToMany 関連付け)がキャッシュ可能であることを明示的に示す必要があることを既に説明しました。それ以外の場合はキャッシュされません。
実際には、Hibernateはコレクションをそれぞれのコレクションに対して1つの別々のキャッシュ領域に格納します。領域名は、完全修飾クラス名にコレクションプロパティの名前を加えたものです。次に例を示します。
org.baeldung.persistence.model.Foo.bars 。これにより、コレクション用の個別のキャッシュパラメータ、つまり排除/有効期限ポリシーを柔軟に定義できます。
また、コレクションに含まれるエンティティのIDのみが各コレクションエントリに対してキャッシュされることに注意することは重要です。つまり、ほとんどの場合、含まれるエンティティもキャッシュ可能にすることをお勧めします。
10 HQL DMLスタイルのクエリとネイティブクエリのキャッシュ無効化
DMLスタイルのHQL( insert 、 update 、および delete HQLステートメント)になると、Hibernateはどのエンティティがそのような操作の影響を受けるかを判断できます。
entityManager.createQuery("update Foo set ... where ...").executeUpdate();
この場合、すべてのFooインスタンスはL2キャッシュから追い出されますが、他のキャッシュされたコンテンツは変更されません。
しかし、ネイティブのSQL DMLステートメントに関して言えば、Hibernateは何が更新されているのかを推測することができないので、2次キャッシュ全体を無効にします。
session.createNativeQuery("update FOO set ... where ...").executeUpdate();
これはおそらくあなたが望むものではありません!解決策はHibernateにどのエンティティがネイティブDMLステートメントの影響を受けているかを伝えることです。そうすれば Foo エンティティに関連するエントリだけを削除できます。
Query nativeQuery = entityManager.createNativeQuery("update FOO set ... where ...");
nativeQuery.unwrap(org.hibernate.SQLQuery.class).addSynchronizedEntityClass(Foo.class);
nativeQuery.executeUpdate();
この機能はJPAでは(まだ)定義されていないため、HibernateのネイティブSQLSQL APIにもフォールバックしました。
上記はDMLステートメント( 挿入 、 更新 、 削除 、およびネイティブ関数/プロシージャー呼び出し)にのみ適用されることに注意してください。ネイティブ 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. クエリキャッシュのベストプラクティス
クエリキャッシングに関連するいくつかのガイドラインとベストプラクティスは次のとおりです。
-
コレクションの場合と同様に、結果として返されるエンティティのIDのみ
キャッシュ可能なクエリの一部はキャッシュされるため、そのようなエンティティに対しては2次キャッシュを有効にすることを強くお勧めします。
-
クエリパラメータの組み合わせごとに1つのキャッシュエントリがあります
クエリごとに値(バインド変数)を設定するため、さまざまな組み合わせのパラメータ値を想定しているクエリはキャッシュには適しません。
-
頻繁に存在するエンティティクラスを含むクエリ
データベースに加えられた変更もキャッシングには適していません。変更されたインスタンスがクエリー結果の一部としてキャッシュされているかどうかにかかわらず、クエリーに参加しているエンティティクラスに関連する変更があると無効になります。
-
デフォルトでは、すべてのクエリキャッシュ結果はに保存されます。
org.hibernate.cache.internal.StandardQueryCache region。エンティティ/コレクションキャッシュと同様に、このリージョンのキャッシュパラメータをカスタマイズして、ニーズに応じて削除ポリシーと有効期限ポリシーを定義できます。各クエリに対して、異なるクエリに異なる設定を提供するためにカスタムリージョン名を指定することもできます。
-
キャッシュ可能なクエリの一部としてクエリされるすべてのテーブルに対して、
Hibernateは最後の更新タイムスタンプを org.hibernate.cache.spi.UpdateTimestampsCache という名前の別の領域に保存します。 Hibernateはキャッシュされたクエリ結果が古くなっていないことを確認するためにこの領域を使用するため、クエリキャッシュを使用する場合、この領域を認識することは非常に重要です。このキャッシュ内のエントリーは、照会結果領域内の対応する表について照会結果がキャッシュに入れられている限り、追い出し/期限切れにしてはなりません。とにかく多くのメモリを消費しないので、このキャッシュ領域の自動追い出しと有効期限をオフにするのが最善です。
12. 結論
この記事では、Hibernateの2次キャッシュを設定する方法について説明しました。
Hibernateは舞台裏で重い作業をすべて行っているため、第2レベルのキャッシュ使用率がアプリケーションビジネスロジックに透過的になるため、設定と使用がかなり簡単であることがわかりました。
このHibernate二次キャッシュチュートリアルの実装はhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/spring-jpa[Github]で利用可能です。これはMavenベースのプロジェクトなので、そのままインポートして実行するのは簡単なはずです。