webentwicklung-frage-antwort-db.com.de

Wie kann man JPA-Entitäten aktualisieren, wenn sich die Backend-Datenbank asynchron ändert?

Ich habe eine PostgreSQL 8.4-Datenbank mit einigen Tabellen und Ansichten, die im Wesentlichen Joins in einigen Tabellen sind. Ich habe NetBeans 7.2 (wie here ) verwendet, um auf REST basierende Dienste zu erstellen, die von diesen Ansichten und Tabellen abgeleitet wurden, und diese auf einem Glassfish 3.1.2.2-Server bereitgestellt haben.

Es gibt einen weiteren Prozess, der den Inhalt einiger Tabellen, die zum Erstellen der Ansichten verwendet werden, asynchron aktualisiert. Ich kann die Ansichten und Tabellen direkt abfragen und sehen, dass diese Änderungen korrekt vorgenommen wurden. Beim Abrufen aus REST - basierten Diensten stimmen die Werte jedoch nicht mit denen in der Datenbank überein. Ich gehe davon aus, dass JPA lokale Kopien des Datenbankinhalts auf dem Glassfish-Server zwischengespeichert hat und JPA die zugehörigen Entitäten aktualisieren muss.

Ich habe versucht, der AbstractFacade-Klasse, die NetBeans generiert, ein paar Methoden hinzuzufügen:

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;
        }
    }

...

}

Ich rufe dann doRefresh() von jeder der find-Methoden auf, die NetBeans generiert. Normalerweise passiert die Variable IllegalArgumentsException etwas wie Can not refresh not managed object: [email protected]:MyView [ javaType: class org.my.rest.MyView descriptor: RelationalDescriptor(org.my.rest.MyView --> [DatabaseTable(my_view)]), mappings: 12]..

Ich suche also nach einigen Vorschlägen, wie die mit den Ansichten verknüpften Entitäten korrekt aktualisiert werden können, damit sie auf dem neuesten Stand ist.

AKTUALISIEREN: Es stellte sich heraus, dass mein Verständnis des zugrunde liegenden Problems nicht korrekt war. Es ist etwas mit einer anderen Frage, die ich zuvor gestellt habe verwandt, nämlich dass die Ansicht kein einzelnes Feld hatte, das als eindeutige Kennung verwendet werden könnte. NetBeans erforderlich Ich wähle ein ID-Feld aus, also wählte ich nur einen Teil eines Schlüssels aus, der mehrteilig sein sollte. Dies zeigte das Verhalten, dass alle Datensätze mit einem bestimmten ID-Feld identisch waren, obwohl die Datenbank Datensätze mit demselben ID-Feld hatte, der Rest jedoch anders war. JPA ging nicht weiter, als nachzuschauen, was ich gesagt habe, es sei die eindeutige Kennung und habe einfach die erste gefundene Platte gezogen.

Ich habe das Problem gelöst, indem ich ein eindeutiges Bezeichnerfeld hinzugefügt habe (der multipart-Schlüssel konnte nie richtig funktionieren).

31
andand

Ich empfehle, eine @Startup@Singleton-Klasse hinzuzufügen, die eine JDBC-Verbindung zur PostgreSQL-Datenbank herstellt und LISTEN UND NOTIFY verwendet, um die Cache-Ungültigkeit zu behandeln.

Update: Hier ist ein weiterer interessanter Ansatz, der pgq und eine Gruppe von Arbeitern für Invalidität verwendet .

Invalidierungssignalisierung

Fügen Sie einen Trigger für die zu aktualisierende Tabelle hinzu, der bei jeder Aktualisierung einer Entität eine NOTIFY sendet. In PostgreSQL 9.0 und höher kann NOTIFY eine Payload enthalten, in der Regel eine Zeilen-ID. Sie müssen also nicht den gesamten Cache, sondern nur die Entität, die sich geändert hat, ungültig machen. In älteren Versionen, in denen keine Nutzdaten unterstützt werden, können Sie die ungültig gemachten Einträge entweder einer zeitgestempelten Protokolltabelle hinzufügen, die Ihre Hilfsklasse abfragt, wenn eine NOTIFY abgerufen wird, oder den gesamten Cache einfach ungültig machen.

Ihre Helfer-Klasse LISTENs jetzt auf die NOTIFY-Ereignisse, die der Trigger sendet. Wenn es ein NOTIFY -Ereignis erhält, kann es einzelne Cache-Einträge ungültig machen (siehe unten) oder den gesamten Cache leeren. Mit PgJDBCs Listen/notify-Support können Sie auf Benachrichtigungen aus der Datenbank warten. Sie müssen jeden verwalteten Verbindungspooler Java.sql.Connection auspacken, um zur zugrunde liegenden PostgreSQL-Implementierung zu gelangen, sodass Sie ihn in org.postgresql.PGConnection umwandeln und getNotifications() aufrufen können.

Als Alternative zu LISTEN und NOTIFY können Sie eine Änderungsprotokolltabelle für einen Timer abfragen und einen Trigger für die Problemtabelle verwenden, der geänderte Zeilen-IDs anfügt und Zeitstempel in die Änderungsprotokolltabelle ändern. Dieser Ansatz ist portabel, abgesehen von der Notwendigkeit eines unterschiedlichen Triggers für jeden DB-Typ, er ist jedoch ineffizient und weniger zeitgemäß. Dies erfordert häufiges ineffizientes Abfragen und hat immer noch eine Zeitverzögerung, die der Listen/Notification-Ansatz nicht tut. In PostgreSQL können Sie eine UNLOGGED-Tabelle verwenden, um die Kosten dieses Ansatzes etwas zu reduzieren.

Cache-Ebenen

EclipseLink/JPA verfügt über mehrere Cache-Ebenen.

Der Cache der ersten Ebene befindet sich auf der Ebene EntityManager . Wenn eine Entität durch persist(...), merge(...), find(...) usw. an eine EntityManager angehängt ist, muss die EntityManagerdieselbe Instanz dieser Entität zurückgeben, wenn innerhalb derselben Sitzung erneut auf sie zugegriffen wird Anwendung hat noch Verweise darauf. Diese angefügte Instanz ist nicht auf dem neuesten Stand, wenn sich Ihre Datenbankinhalte seitdem geändert haben.

Der Cache der zweiten Ebene, der optional ist, befindet sich auf der Ebene EntityManagerFactory und ist ein herkömmlicherer Cache. Es ist nicht klar, ob der 2nd Level Cache aktiviert ist. Überprüfen Sie Ihre EclipseLink-Protokolle und Ihren persistence.xml. Mit EntityManagerFactory.getCache() erhalten Sie Zugriff auf den Cache der zweiten Ebene. siehe Cache .

@thedayofcondor zeigte, wie man den 2nd Level Cache mit:

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

sie können aber auch einzelne Objekte mit dem Aufruf evict(Java.lang.Class cls, Java.lang.Object primaryKey) löschen:

em.getEntityManagerFactory().getCache().evict(theClass, thePrimaryKey);

die Sie in Ihrem Listener @Startup@SingletonNOTIFY verwenden können, um nur die Einträge zu annullieren, die sich geändert haben.

Der 1st-Level-Cache ist nicht so einfach, weil er Teil Ihrer Anwendungslogik ist. Sie möchten etwas über die Funktionsweise von EntityManager, verbundenen und getrennten Entitäten usw. erfahren. Eine Option ist, immer getrennte Entitäten für die betreffende Tabelle zu verwenden, wo Sie beim Abrufen der Entität eine neue EntityManager verwenden. Diese Frage:

Ungültige JPA-EntityManager-Sitzung

enthält nützliche Informationen zum Umgang mit der Ungültigmachung des Cache des Entitätsmanagers. Es ist jedoch unwahrscheinlich, dass ein EntityManager-Cache Ihr Problem ist, da ein RESTful-Webdienst normalerweise mit kurzen EntityManager-Sitzungen implementiert wird. Dies ist wahrscheinlich nur dann ein Problem, wenn Sie erweiterte Persistenzkontexte verwenden oder wenn Sie Ihre eigenen EntityManager-Sitzungen erstellen und verwalten und keine durch Container verwaltete Persistenz verwenden.

50
Craig Ringer

Sie können die Zwischenspeicherung entweder vollständig deaktivieren (siehe: http://wiki.Eclipse.org/EclipseLink/FAQ/How_to_disable_the_shared_cache%3F ), sich jedoch auf einen ziemlich großen Leistungsverlust einstellen.

Andernfalls können Sie einen programmgesteuerten Cache mit programmieren 

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

Sie können es einem Servlet zuordnen, sodass Sie es extern aufrufen können. Dies ist besser, wenn Ihre Datenbank äußerlich sehr selten geändert wird und Sie nur sicher gehen möchten, dass JPS die neue Version verwendet

8
thedayofcondor

Nur ein Gedanke, aber wie erhalten Sie Ihre EntityManager/Session/was auch immer? 

Wenn Sie die Entität in einer Sitzung abgefragt haben, wird sie in der nächsten Sitzung getrennt und Sie müssen sie wieder in den Persistenzkontext einfügen, um sie erneut verwalten zu können. 

Wenn Sie versuchen, mit getrennten Entitäten zu arbeiten, kann dies zu nicht verwalteten Ausnahmen führen. Sie sollten die Entität erneut abfragen, oder Sie versuchen es mit der Zusammenführung (oder ähnlichen Methoden).

0
Volker