web-dev-qa-db-ja.com

バックエンドデータベースが非同期に変更されたときにJPAエンティティを更新する方法

基本的にいくつかのテーブルで結合されているいくつかのテーブルとビューを持つ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()を呼び出します。通常起こることは、IllegalArgumentsExceptionCan 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は、一意の識別子であると私が言ったものを見て、それが見つけた最初のレコードを単純にプルするだけでした。

一意の識別子フィールドを追加することでこれを解決しました(マルチパートキーを適切に機能させることができませんでした)。

35
andand

PostgreSQLデータベースへのJDBC接続を確立し、 LISTENおよびNOTIFY を使用してキャッシュの無効化を処理する_@Startup_ _@Singleton_クラスを追加することをお勧めします。

Updatepgqと無効化のためにワーカーのコレクションを使用する別の興味深いアプローチ

無効化シグナル

エンティティが更新されるたびにNOTIFYを送信する更新中のテーブルにトリガーを追加します。 PostgreSQL 9.0以降では、このNOTIFYにペイロード(通常は行ID)を含めることができるため、キャッシュ全体を無効にする必要はなく、変更されたエンティティのみを無効にする必要があります。ペイロードがサポートされていない古いバージョンでは、ヘルパークラスがNOTIFYを取得したときにクエリするタイムスタンプ付きログテーブルに無効化されたエントリを追加するか、キャッシュ全体を無効化できます。

ヘルパークラスは、トリガーが送信するLISTENイベントのNOTIFYsになりました。 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を使用します。この質問:

JPA EntityManagerセッションの無効化

エンティティマネージャのキャッシュの無効化の処理に関する有用な議論があります。ただし、RESTful Webサービスは通常短いEntityManagerセッションを使用して実装されるため、EntityManagerキャッシュが問題になることはほとんどありません。これは、拡張持続性コンテキストを使用している場合、またはコンテナ管理の持続性を使用するのではなく、独自のEntityManagerセッションを作成および管理している場合にのみ問題となる可能性があります。

52
Craig Ringer

キャッシュを完全に無効にすることもできます(参照: http://wiki.Eclipse.org/EclipseLink/FAQ/How_to_disable_the_shared_cache%3F )が、かなり大きなパフォーマンスの低下に備えてください。

それ以外の場合は、プログラムでクリアキャッシュを実行できます。

em.getEntityManagerFactory().getCache().evictAll();

それをサーブレットにマップして外部から呼び出すことができます-これは、データベースが外部でほとんど変更されず、JPSが新しいバージョンを選択することを確認したい場合に適しています

8
thedayofcondor

ただの考えですが、どのようにしてEntityManager/Session/whateverを受け取りますか?

あるセッションでエンティティを照会した場合、次のセッションでエンティティが切り離され、永続コンテキストに再度マージして管理し直す必要があります。

デタッチされたエンティティを操作しようとすると、管理されていない例外が発生する可能性があります。エンティティを再クエリするか、マージ(または同様の方法)で試すことができます。

0
Volker