基本的にいくつかのテーブルで結合されているいくつかのテーブルとビューを持つPostgreSQL 8.4データベースがあります。 NetBeans 7.2を使用して( ここ で説明)、これらのビューおよびテーブルから派生したRESTベースのサービスを作成し、Glassfish 3.1.2.2サーバーにデプロイしました。
ビューを構築するために使用されるいくつかのテーブルのコンテンツを非同期的に更新する別のプロセスがあります。ビューとテーブルを直接照会して、これらの変更が正しく行われたことを確認できます。ただし、RESTベースのサービスから取得した場合、値はデータベースの値と同じではありません。JPAがGlassfishサーバーにデータベースコンテンツのローカルコピーをキャッシュしているためと思われますJPAは関連するエンティティを更新する必要があります。
NetBeansが生成するAbstractFacadeクラスにいくつかのメソッドを追加してみました。
_public abstract class AbstractFacade<T> {
private Class<T> entityClass;
private String entityName;
private static boolean _refresh = true;
public static void refresh() { _refresh = true; }
public AbstractFacade(Class<T> entityClass) {
this.entityClass = entityClass;
this.entityName = entityClass.getSimpleName();
}
private void doRefresh() {
if (_refresh) {
EntityManager em = getEntityManager();
em.flush();
for (EntityType<?> entity : em.getMetamodel().getEntities()) {
if (entity.getName().contains(entityName)) {
try {
em.refresh(entity);
// log success
}
catch (IllegalArgumentException e) {
// log failure ... typically complains entity is not managed
}
}
}
_refresh = false;
}
}
...
}
_
次に、NetBeansが生成するfind
メソッドのそれぞれからdoRefresh()
を呼び出します。通常起こることは、IllegalArgumentsException
がCan not refresh not managed object: EntityTypeImpl@28524907:MyView [ javaType: class org.my.rest.MyView descriptor: RelationalDescriptor(org.my.rest.MyView --> [DatabaseTable(my_view)]), mappings: 12].
のような何かを示すようにスローされることです。
したがって、ビューに関連付けられているエンティティを最新の状態に更新する方法についての提案を探しています。
更新: 根本的な問題に対する私の理解が正しくなかったことが判明しました。 以前に投稿した別の質問 にある程度関連しています。つまり、ビューには一意の識別子として使用できる単一のフィールドがありませんでした。 NetBeansではIDフィールドを選択する必要があるため、マルチパートキーにすべき部分の1つを選択しました。これは、データベースに同じIDフィールドを持つレコードがあり、残りは異なっていても、特定のIDフィールドを持つすべてのレコードが同一であるという動作を示しました。 JPAは、一意の識別子であると私が言ったものを見て、それが見つけた最初のレコードを単純にプルするだけでした。
一意の識別子フィールドを追加することでこれを解決しました(マルチパートキーを適切に機能させることができませんでした)。
PostgreSQLデータベースへのJDBC接続を確立し、 LISTEN
およびNOTIFY
を使用してキャッシュの無効化を処理する_@Startup
_ _@Singleton
_クラスを追加することをお勧めします。
Update: pgqと無効化のためにワーカーのコレクションを使用する別の興味深いアプローチ 。
エンティティが更新されるたびにNOTIFY
を送信する更新中のテーブルにトリガーを追加します。 PostgreSQL 9.0以降では、このNOTIFY
にペイロード(通常は行ID)を含めることができるため、キャッシュ全体を無効にする必要はなく、変更されたエンティティのみを無効にする必要があります。ペイロードがサポートされていない古いバージョンでは、ヘルパークラスがNOTIFY
を取得したときにクエリするタイムスタンプ付きログテーブルに無効化されたエントリを追加するか、キャッシュ全体を無効化できます。
ヘルパークラスは、トリガーが送信するLISTEN
イベントのNOTIFY
sになりました。 NOTIFY
イベントを取得すると、個々のキャッシュエントリを無効にするか(以下を参照)、キャッシュ全体をフラッシュできます。 PgJDBCのlisten/notifyサポート を使用して、データベースからの通知をリッスンできます。基礎となるPostgreSQL実装を取得するには、_Java.sql.Connection
_で管理されている接続プーラーのラップを解除して、_org.postgresql.PGConnection
_にキャストし、getNotifications()
を呼び出す必要があります。
LISTEN
およびNOTIFY
の代わりに、タイマーで変更ログテーブルをポーリングし、問題テーブルでトリガーを使用して、変更された行IDと変更タイムスタンプを変更ログテーブルに追加することができます。このアプローチは、DBタイプごとに異なるトリガーが必要な場合を除き、移植可能ですが、非効率的でタイムリーではありません。頻繁に非効率的なポーリングが必要になりますが、listen/notifyアプローチにはない時間遅延があります。 PostgreSQLでは、UNLOGGED
テーブルを使用して、このアプローチのコストを少し削減できます。
EclipseLink/JPAには、2つのレベルのキャッシュがあります。
1次キャッシュは EntityManager
レベルにあります。エンティティがpersist(...)
、merge(...)
、find(...)
などによってEntityManager
に接続されている場合、EntityManager
が必要ですアプリケーションがまだそれへの参照を持っているかどうかにかかわらず、同じセッション内で再度アクセスされたときにそのエンティティの同じインスタンスを返します。データベースの内容が変更された場合、この添付されたインスタンスは最新のものではありません。
オプションの2次キャッシュは EntityManagerFactory
レベルであり、より伝統的なキャッシュです。 2次キャッシュが有効になっているかどうかは明らかではありません。 EclipseLinkログと_persistence.xml
_を確認してください。 EntityManagerFactory.getCache()
;で2次キャッシュにアクセスできます。 Cache
を参照してください。
@thedayofcondorは、2次キャッシュを次の方法でフラッシュする方法を示しました。
_em.getEntityManagerFactory().getCache().evictAll();
_
しかし、 evict(Java.lang.Class cls, Java.lang.Object primaryKey)
呼び出しで個々のオブジェクトを削除することもできます:
_em.getEntityManagerFactory().getCache().evict(theClass, thePrimaryKey);
_
これを_@Startup
_ _@Singleton
_ NOTIFY
リスナーから使用して、変更されたエントリのみを無効にすることができます。
1次キャッシュはアプリケーションロジックの一部であるため、それほど簡単ではありません。 EntityManager
、アタッチされたエンティティ、デタッチされたエンティティなどの仕組みについて学びたいと思うでしょう。 1つのオプションは、対象のテーブルに対して常に独立したエンティティを使用することです。この場合、エンティティをフェッチするたびに新しいEntityManager
を使用します。この質問:
エンティティマネージャのキャッシュの無効化の処理に関する有用な議論があります。ただし、RESTful Webサービスは通常短いEntityManager
セッションを使用して実装されるため、EntityManager
キャッシュが問題になることはほとんどありません。これは、拡張持続性コンテキストを使用している場合、またはコンテナ管理の持続性を使用するのではなく、独自のEntityManager
セッションを作成および管理している場合にのみ問題となる可能性があります。
キャッシュを完全に無効にすることもできます(参照: http://wiki.Eclipse.org/EclipseLink/FAQ/How_to_disable_the_shared_cache%3F )が、かなり大きなパフォーマンスの低下に備えてください。
それ以外の場合は、プログラムでクリアキャッシュを実行できます。
em.getEntityManagerFactory().getCache().evictAll();
それをサーブレットにマップして外部から呼び出すことができます-これは、データベースが外部でほとんど変更されず、JPSが新しいバージョンを選択することを確認したい場合に適しています
ただの考えですが、どのようにしてEntityManager/Session/whateverを受け取りますか?
あるセッションでエンティティを照会した場合、次のセッションでエンティティが切り離され、永続コンテキストに再度マージして管理し直す必要があります。
デタッチされたエンティティを操作しようとすると、管理されていない例外が発生する可能性があります。エンティティを再クエリするか、マージ(または同様の方法)で試すことができます。