38

基本的にいくつかのテーブルの結合であるいくつかのテーブルとビューを持つ 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 が生成するdoRefresh()各メソッドから呼び出します。find通常何が起こるかは、次の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 フィールドを選択したので、マルチパート キーであるべきものの一部を選択しただけです。これは、データベースに同じ ID フィールドを持つレコードがあり、残りのレコードが異なる場合でも、特定の ID フィールドを持つすべてのレコードが同一であるという動作を示しました。JPA は、一意の識別子であると私が言ったものを調べるだけで、最初に見つかったレコードを取得するだけでした。

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

4

4 に答える 4

56

@Startup @SingletonPostgreSQL データベースへの JDBC 接続を確立し、 と を使用LISTENNOTIFYてキャッシュの無効化を処理するクラスを追加することをお勧めします。

更新:これは、pgq と無効化のためのワーカーのコレクションを使用する、別の興味深いアプローチです。

無効化シグナリング

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

ヘルパー クラスは、トリガーが送信LISTENするイベントに対して s になりました。NOTIFYイベントを取得すると、NOTIFY個々のキャッシュ エントリを無効にするか (以下を参照)、キャッシュ全体をフラッシュできます。PgJDBC のリッスン/通知サポートを使用して、データベースからの通知をリッスンできます。基になる PostgreSQL 実装にアクセスするために管理されている接続プーラーをアンラップして、キャストして呼び出すjava.sql.Connectionことができるようにする必要があります。org.postgresql.PGConnectiongetNotifications()

LISTENとの代わりにNOTIFY、タイマーで変更ログ テーブルをポーリングし、問題のテーブルにトリガーを設定して、変更された行 ID と変更タイムスタンプを変更ログ テーブルに追加することができます。このアプローチは、DB タイプごとに異なるトリガーが必要になることを除けば移植可能ですが、非効率的でタイムリーではありません。非効率なポーリングを頻繁に行う必要があり、listen/notify アプローチでは発生しない遅延が発生します。PostgreSQL では、UNLOGGEDテーブルを使用して、このアプローチのコストを少し削減できます。

キャッシュレベル

EclipseLink/JPA には、いくつかのレベルのキャッシングがあります。

第 1 レベルのキャッシュはEntityManagerレベルにあります。EntityManagerエンティティがby persist(...)merge(...)find(...)などにアタッチされている場合、同じセッション内で再度アクセスされたときに、アプリケーションがまだそのエンティティへの参照を持っているかどうかにかかわらず、 はそのエンティティの同じインスタンスEntityManagerを返す必要があります。データベースの内容が変更された場合、このアタッチされたインスタンスは最新ではありません。

オプションの第 2 レベル キャッシュはEntityManagerFactoryレベルにあり、より伝統的なキャッシュです。2 次キャッシュが有効になっているかどうかは不明です。EclipseLink ログとpersistence.xml. で第 2 レベルのキャッシュにアクセスできますEntityManagerFactory.getCache()。を参照してください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 セッションの無効化

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

于 2012-11-07T00:11:24.457 に答える
8

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

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

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

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

于 2012-11-06T20:43:09.263 に答える
0

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

1 つのセッションでエンティティをクエリした場合、エンティティは次のセッションで切り離され、再び管理されるように永続化コンテキストにマージする必要があります。

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

于 2012-11-06T20:57:47.350 に答える
-4

JPA は、デフォルトではキャッシュを行いません。明示的に構成する必要があります。これは、あなたが選択したアーキテクチャ スタイルである REST の副作用だと思います。Web サーバー、プロキシ サーバーなどでキャッシュが発生していると思います。これを読んで、さらにデバッグすることをお勧めします。

于 2012-11-06T20:44:25.557 に答える