Hibernate Cache。 練習する

そのため、 前の記事の続きで、現実のプロジェクトで働いていたときに抱えていた現実の問題についてお話します。



移行スクリプト


アプリケーションでキャッシュを操作する際の最も一般的な問題の1つは、実行中のサーバーに移行スクリプトをロールする必要があることです。 実際、これらのスクリプトが稼働中のサーバーのファクトリセッションを介して実行されない場合、このファクトリのキャッシュはデータベースに加えられた変更を認識しません。 したがって、データの非互換性の問題が発生します。 この問題を解決するにはいくつかの方法があります。

  1. サーバーの再起動は最も簡単で、通常は最も受け入れられない方法です。
  2. 特定のメカニズムを使用してキャッシュをクリアすることは、おそらく単純さと信頼性の観点から最も最適な方法です。 このメソッドは、たとえばJMXでWebページまたは他のインターフェイスに移動し、必要に応じて呼び出すことができます。 メソッドの柔軟性は、一度書かれただけで、いつでもどこでも使用できることです。 キャッシュプロバイダーがEHCacheで、プロバイダークラスがSingletonEhCacheProviderの場合、コードは次のようになります。

    public String dumpKeys() { String regions[] = CacheManager.getInstance().getCacheNames(); StringBuilder allkeys = new StringBuilder(); String newLine = System.getProperty("line.separator"); for (String region : regions) { Ehcache cache = CacheManager.getInstance().getEhcache(region); allkeys.append(toSomeReadableString(cache.getKeys())); allkeys.append(newLine); } return allkeys.toString(); }
          
          





    当然、このコードは、統計情報を追跡する休止状態と同じプロセスで実行する必要があります。 詳細はこちらをご覧ください 。 セッションファクトリを使用しても同じことが実現できます。

  3. セッションサーバーファクトリを使用した移行スクリプトの実行。 これは2番目の方法と似ていますが、唯一の違いはキャッシュをクリアせず、すべての移行スクリプトを既存のファクトリーに渡すことです。 したがって、必要なキャッシュはすべて自分自身で更新されます。 キャッシュが大きく、新しいキャッシュを作成するよりも更新する方が安価な場合は、この方法を使用するのが合理的です。




クエリキャッシュ


クエリキャッシュは、おそらく前の記事に記載されたすべてのキャッシュの中で最も非効率的です。 これにはいくつかの理由があります。

  1. まず第一に(最初の記事で言及するのを忘れていた)、このキャッシュ内のデータのキーは、要求パラメーターだけでなく、要求自体です。 これは、リクエストが多数あり、それらが大きい場合に特に重要です。
  2. 多くの場合、クエリキャッシュはフラッシュされます。 つまり、クエリに参加するテーブルの少なくとも1つが変更された場合、キャッシュがリセットされ、クエリが再度実行されます。


したがって、非常に慎重に使用する必要があります。 そして覚えておいてください-すべてを連続してキャッシュすることは意味がありません。 アプリケーションを実際に高速化できる要求と、キャッシュがほとんどリセットされない要求のみをキャッシュします。

クエリキャッシュの不適切な場所の典型的な例は、エンティティの更新/追加のレートが高いリソース上の何かの量をフェッチすることです。 アプリケーションで作成されたエンティティの統計を、ステータスやその他のパラメーターによって表示する必要があるとします。たとえば、次のようなものです。

  Criteria criteria = getSession().createCriteria(Plan.class); criteria.setProjection(Projections.projectionList() .add(Projections.groupProperty("status")) .add(Projections.rowCount()) ); criteria.setCacheable(true);
      
      





テーブルに新しいプランを挿入するか、既存のプランを変更するたびに、キャッシュがリセットされます。 それだけでなく、キャッシュは常にフラッシュされるだけでなく、テーブルの状態を監視し、キャッシュ自体を維持するための一定のコストもあります。 これはパフォーマンスに劇的な影響を与える可能性があります。 実際、これは私のアプリケーションで一度起こりました。



キャッシュされたアイテムを削除する


キャッシュでの作業中に発生した問題の特定の状況をすべて思い出せなくなりました。 しかし、そのうちの1つを特によく覚えています-キャッシュされたコレクションにあるオブジェクトを削除します。 オブジェクトとその依存関係は別々にキャッシュされることが知られています。 したがって、次のクラスがある場合:

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





そして、ユーザーの1人が削除された後、その削除を実行します。

 getSession().delete(user)
      
      





この場合、削除されたエントリはユーザーコレクションのキャッシュに残ります。 一貫性のないデータを取得します。 同じことが、あらゆる種類のカスケード削除にも当てはまります。 カスケードで削除されたオブジェクトがキャッシュに残ったとき。 明らかな解決策の1つは、オブジェクトが存在するコレクションキャッシュからオブジェクトを削除することです。 つまり、この例では、削除は次のようになります。

 SharedDoc doc = (SharedDoc) session.load(SharedDoc.class, 1L); doc.getUsers().remove(user); session.delete(user);
      
      





これは、ユーザーがコレクションキャッシュ(ユーザー)に1つしかない場合に最適です。 しかし、そのようなコレクションがたくさんあり、それらが異なるエンティティに散在している場合、タスクは非常に複雑です。 同様の種類の問題は、キャッシュに残っているオブジェクトでアクションを試行するときにObjectNotFoundExceptionを引き起こす可能性もあります。



競争力のある取引


競合するトランザクションの場合、キャッシュが期待どおりに動作しない場合があります。 典型的なケースを考えてみましょう:

 Session session1 = getSession(); Session session2 = getSession()); Transaction t = session1.beginTransaction(); Plan plan = (Plan) session1.load(Plan.class, 1L); System.out.println (plan.getName()); plan.setName(newName); t.commit(); t = session2.beginTransaction(); plan = (Plan) session2.load(Plan.class, 1L); System.out.println (plan.getName()); tx2.commit(); session1.close(); session2.close();
      
      





プランの2回目の呼び出しでは、2次レベルのキャッシュから取得する必要がありますが、そうではありません。 session2オブジェクトはプランオブジェクトを変更する前に作成されたため。 また、2次キャッシュにアクセスしている間、Hibernateはセッションの作成時間とオブジェクトが最後に変更された時間を検証します。 この振る舞いは、アプリケーションで考慮する必要があります。この振る舞いは、あなたが期待しない場所で追加の負荷を作成する可能性があるためです。



ここに、おそらく、Hibernate Cacheを操作するときの問題から思い出すことができたすべてのものがあります。 残念ながら、分散キャッシュを使用していなかったため、このトピックについては何も言いません。 記事に記載されていない他の問題が発生した場合は、コメントを追加します。



All Articles