休止状態のキャッシュ

多くの場合、Javaアプリケーションでは、データベースの負荷を軽減するために、キャッシュを使用します。 キャッシュが内部でどのように機能するかを本当に理解している人は多くありません。注釈を追加するだけでは必ずしも十分ではありません。システムがどのように機能するかを理解する必要があります。 したがって、この記事では、人気のあるORMフレームワークのキャッシュがどのように機能するかというトピックを拡張してみます。 だから、最初に、少し理論。



まず、Hibernateキャッシュは3つのレベルのキャッシュです。



一次キャッシュ


一次キャッシュは常にセッションオブジェクトにアタッチされます。 Hibernateはデフォルトで常にこのキャッシュを使用し、無効にすることはできません。 次のコードをすぐに見てみましょう。

SharedDoc persistedDoc = (SharedDoc) session.load(SharedDoc.class, docId); System.out.println(persistedDoc.getName()); user1.setDoc(persistedDoc); persistedDoc = (SharedDoc) session.load(SharedDoc.class, docId); System.out.println(persistedDoc.getName()); user2.setDoc(persistedDoc);
      
      





おそらく、データベースで2つのクエリが実行されることを期待していますか? そうではありません。 この例では、1つのセッションのコンテキストでこれらの呼び出しが発生するため、load()の呼び出しが2回行われるにもかかわらず、データベースへの1つのクエリが実行されます。 同じ識別子でプランをロードする2回目の試行中に、セッションキャッシュが使用されます。

1つの重要なポイント-load()メソッドを使用する場合、Hibernateは必要になるまでデータベースからデータをアンロードしません。 つまり、最初の読み込み呼び出しが行われた時点で、プロキシオブジェクトまたはデータが既にセッションキャッシュにあった場合はデータ自体を取得します。 したがって、データベースにデータを100%プルするために、コードにgetName()が存在します。 また、潜在的な最適化の絶好の機会を提供します。 プロキシオブジェクトの場合、get()メソッドとは対照的に、データベースにリクエストを行うことなく2つのオブジェクトを接続できます。 save()、update()、saveOrUpdate()、load()、get()、list()、iterate()、scroll()メソッドを使用する場合、常に第1レベルのキャッシュが使用されます。 実際、追加するものはこれ以上ありません。



二次キャッシュ


1次キャッシュがセッションオブジェクトに関連付けられている場合、2次キャッシュはセッションファクトリオブジェクトに関連付けられています。 つまり、このキャッシュの可視性は第1レベルのキャッシュよりもはるかに広いことを意味します。 例:

 Session session = factory.openSession(); SharedDoc doc = (SharedDoc) session.load(SharedDoc.class, 1L); System.out.println(doc.getName()); session.close(); session = factory.openSession(); doc = (SharedDoc) session.load(SharedDoc.class, 1L); System.out.println(doc.getName()); session.close();
      
      





この例では、データベースへの2つのクエリが実行されます。これは、デフォルトで2次キャッシュが無効になっているためです。 有効にするには、JPA構成ファイル(persistence.xml)に次の行を追加する必要があります。

 <property name="hibernate.cache.provider_class" value="net.sf.ehcache.hibernate.SingletonEhCacheProvider"/> //     //<property name="hibernate.cache.provider_class" value="org.hibernate.cache.EhCacheProvider"/> <property name="hibernate.cache.use_second_level_cache" value="true"/>
      
      





最初の行に注意してください。 実際、hibernate自体はキャッシングを実装していません。 実装のための構造を提供するだけなので、ORMフレームワークの仕様を満たす任意の実装を接続できます。 一般的な実装のうち、 次のものを区別できます。



これに加えて、ほとんどの場合、キャッシュ実装自体を個別に構成する必要があります。 EHCacheの場合、これはehcache.xmlファイルで実行する必要があります。 結論として、キャッシュする対象を休止状態自体に伝える必要があります。 幸いなことに、これは次のように注釈を使用して非常に簡単に実行できます。

 @Entity @Table(name = "shared_doc") @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class SharedDoc{ private Set<User> users; }
      
      





これらのすべての操作が行われた後にのみ2次キャッシュが有効になり、上記の例ではデータベースへの1つのリクエストのみが実行されます。

言及する必要がある2番目のレベルのキャッシュに関するもう1つの重要な詳細は、hibernateがクラス自体のオブジェクトを保存しないことです。 文字列、数値などの配列の形式で情報を保存します。オブジェクトの識別子は、この情報へのポインタとして機能します。 概念的には、これはMapのようなもので、オブジェクトのIDがキーであり、データ配列が値です。 これを大体想像できます:

 1 -> { "Pupkin", 1, null , {1,2,5} }
      
      





これは、各オブジェクトがどのくらいの余分なメモリを占有するかを考慮すると 、非常に合理的です。

上記に加えて、クラスの依存関係もデフォルトではキャッシュされないことに注意してください。 たとえば、上記のクラス-SharedDocを見ると、ユーザーコレクションを取得するときは、2次キャッシュではなくデータベースから取得されます。 依存関係もキャッシュする場合、クラスは次のようになります。

 @Entity @Table(name = "shared_doc") @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class SharedDoc{ @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) private Set<User> users; }
      
      







最後の詳細-2次キャッシュからの読み取りは、目的のオブジェクトが1次キャッシュで見つからなかった場合にのみ行われます。



クエリキャッシュ


最初の例を次のように書き直します。

 Query query = session.createQuery("from SharedDoc doc where doc.name = :name"); SharedDoc persistedDoc = (SharedDoc) query.setParameter("name", "first").uniqueResult(); System.out.println(persistedDoc.getName()); user1.setDoc(persistedDoc); persistedDoc = (SharedDoc) query.setParameter("name", "first").uniqueResult(); System.out.println(persistedDoc.getName()); user2.setDoc(persistedDoc);
      
      





この種のクエリの結果は、第1レベルまたは第2レベルのキャッシュにも保存されません。 これは、クエリキャッシュを使用できる場所です。 また、デフォルトでは無効になっています。 有効にするには、構成ファイルに次の行を追加する必要があります。

 <property name="hibernate.cache.use_query_cache" value="true"/>
      
      





また、Queryオブジェクトの作成後に追加することにより、上記の例を書き直します(基準についても同様です)。

 Query query = session.createQuery("from SharedDoc doc where doc.name = :name"); query.setCacheable(true);
      
      





クエリキャッシュは、セカンドレベルキャッシュに似ています。 しかし、彼とは異なり、キャッシュデータのキーはオブジェクトの識別子ではなく、クエリパラメーターのセットです。 また、データ自体は、クエリ条件に一致するオブジェクトの識別子です。 したがって、このキャッシュを2次キャッシュで使用するのは合理的です。



キャッシング戦略


キャッシュ戦略は、特定の状況でのキャッシュの動作を決定します。 4つのグループがあります。



詳細はこちらをご覧ください



キャッシュ領域


リージョンまたはリージョンは、キャッシュの論理メモリセパレータです。 リージョンごとに、独自のキャッシュポリシーを構成できます(同じehcache.xmlのEhCacheの場合)。 リージョンが指定されていない場合、デフォルトのリージョンが使用されます。これには、キャッシュが適用されるクラスのフルネームがあります。 コードは次のようになります。

 @Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "STATIC_DATA")
      
      





そして、このようなクエリキャッシュの場合:

 query.setCacheRegion("STATIC_DATA"); //    criteria.setCacheRegion("STATIC_DATA");
      
      







他に何を知る必要がありますか?


アプリケーション開発中、特に最初は、特定のリクエストが実際にキャッシュされているかどうかを確認するのが非常に便利です;このためには、セッションファクトリに次のプロパティを指定する必要があります。

 <property name="hibernate.show_sql" value="true"/> <property name="hibernate.format_sql" value="true"/>
      
      





さらに、セッションファクトリは、すべてのオブジェクト、領域、およびキャッシュの依存関係の使用統計を生成および保存することもできます。

 <property name="hibernate.generate_statistics" value="true"/> <property name="hibernate.cache.use_structured_entries" value="true"/>
      
      





ファクトリにはStatisticsオブジェクトがあり、このセッションにはSessionStatisticsがあります。



セッションメソッド:

flush()-セッションオブジェクトをデータベースと同期し、同時にセッションキャッシュ自体を更新します。

evict()-セッションキャッシュからオブジェクトを削除するために必要です。

contains()-オブジェクトがセッションキャッシュにあるかどうかを判断します。

clear()-キャッシュ全体をクリアします。



おわりに


それだけです。 当然のことながら、この記事以外にも、キャッシュを操作する際に生じるさまざまな微妙な違いや、多くの問題があります。 しかし、これは別の記事のトピックです。



All Articles