CMT を使用する Java EE サーバーでは、ehcache を使用して、ビジネス オブジェクト レイヤー (EJB) とデータ アクセス レイヤー (JDBC を使用する POJO) の間にキャッシュ レイヤーを実装しています。自動入力 Ehcache を使用しているときに、同じレコードにアクセスする 2 つのスレッド間で競合状態が発生しているようです。キャッシュは、レコードの主キーをキーとしています。
シナリオは次のとおりです。
- 最初のスレッドは、データベース内のレコードを更新し、キャッシュからレコードを削除します (ただし、データベースのコミットは必ずしもすぐに行われるとは限りません。他のクエリが続く場合があります)。
- 2 番目のスレッドがレコードを読み取り、キャッシュが再設定されます。
- 最初のスレッドがトランザクションをコミットします。
これはすべて、ほんの一瞬で起こっています。その結果、キャッシュがデータベースと同期しなくなり、その後のレコードの読み取りでは、別の更新が実行されるか、キャッシュからエントリが期限切れになるまで、キャッシュされた古いデータが返されます。古いデータは短期間 (トランザクションの典型的な長さ) は処理できますが、オブジェクトをキャッシュしたい期間である数分は処理できません。
この競合状態を回避するための提案はありますか?
アップデート:
トランザクションがコミットされた後にキャッシュをクリアすることは、確かに理想的です。問題は、CMT を使用する J2EE 環境で、キャッシュ層がビジネス層 (ステートレス セッション EJB) とデータ アクセス層の間に挟まれている場合、これをどのように行うかということです。
これが課す制約を明確にするために、問題のメソッド呼び出しは、前後に発生する追加のメソッド呼び出しと同じトランザクションにある場合とない場合があります。クライアントコードが期待するものからトランザクション境界を変更するため、コミットを強制することはできません (または別のトランザクションでこの作業を行うこともできません)。後続の例外は、トランザクション全体をロールバックしません (この場合、不必要にキャッシュをクリアしても問題ありません)。トランザクションは基本的にクライアントが使用できる API であるため、トランザクションへのエントリ ポイントを制御することはできません。キャッシュをクリアする責任をクライアント アプリケーションに押し付けることは合理的ではありません。
トランザクション全体が EJB コンテナによってコミットされるまでキャッシュ クリア操作を延期できるようにしたいと考えていますが、そのロジックにフックして、ステートレス セッション Bean で独自のコードを実行する方法が見つかりませんでした。
更新#2:
これまでのところ、主要な設計変更を除けば、最も有望なソリューションは、ehcache 2.0 の JTA サポートを使用することです: http://ehcache.org/documentation/apis/jta
これは、ehcache 2.x にアップグレードし、データベースに対しても XA トランザクションを有効にすることを意味しますが、これにはマイナスの副作用が生じる可能性があります。しかし、それは「正しい」方法のようです。