Jak odświeżyć encje JPA, gdy baza danych backend zmienia się asynchronicznie?

Mam bazę danych PostgreSQL 8.4 z niektórymi tabelami i widokami, które są w zasadzie łącznikami na niektórych tabelach. Użyłem NetBeans 7.2 (jak opisano tutaj) do tworzenia usług opartych na REST pochodzących z tych widoków i tabel i wdrożyłem je na serwerze Glassfish 3.1.2.2.

Istnieje inny proces, który asynchronicznie aktualizuje zawartość niektórych tabel używanych do budowania widoków. Mogę bezpośrednio odpytywać widoki i tabele i zobaczyć, że te zmiany zaszły poprawnie. Jednak po pobraniu z usług opartych na REST wartości nie są takie same jak w bazie danych. Zakładam, że dzieje się tak dlatego, że JPA ma buforowane lokalne kopie zawartości bazy danych na serwerze Glassfish i JPA musi odświeżyć powiązane podmioty.

Próbowałem dodać kilka metod do generowanej przez NetBeans klasy 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;
        }
    }

...

}

Następnie wywołuję doRefresh() z każdej z metod find generowanych przez NetBeans. To, co zwykle się dzieje, to IllegalArgumentsException jest wyrzucane somethng like 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].

Więc szukam kilku sugestii, jak poprawnie odświeżyć encje związane z widokami, aby były aktualne.

UPDATE: okazuje się, że moje zrozumienie podstawowego problemu nie było poprawne. Jest to w pewnym sensie związane z innym pytaniem, które napisałem wcześniej , mianowicie widok nie miał jednego pola, które mogłoby być użyte jako unikalny identyfikator. NetBeans wymagane zaznaczam pole ID, więc po prostu wybrałem jedną część tego, co powinno były kluczem wieloczęściowym. Pokazało to zachowanie, że wszystkie rekordy z określonym polem ID były identyczne, nawet jeśli baza danych miała rekordy z tym samym polem ID, ale reszta była inna. JPA nie poszedł dalej niż patrząc na to, co powiedziałem, że jest to unikalny identyfikator i po prostu wyciągnął pierwszy rekord, który znalazł.

Rozwiązałem to, dodając unikalne pole identyfikatora (nigdy nie byłem w stanie uzyskać klucza wieloczęściowego, aby działał poprawnie).

Author: Community, 2012-11-07

4 answers

Polecam dodać @Startup @Singleton klasa, która ustanawia połączenie JDBC z bazą danych PostgreSQL i wykorzystuje LISTEN oraz NOTIFY aby obsłużyć unieważnienie pamięci podręcznej.

Aktualizacja: oto inne ciekawe podejście, wykorzystujące pgq i zbiór pracowników do unieważniania .

Sygnalizacja unieważnienia

Dodaj WYZWALACZ w tabeli, która jest aktualizowana, który wysyła NOTIFY za każdym razem, gdy encja jest aktualizowana. Na PostgreSQL 9.0 i wyżej to NOTIFY może zawierać ładunek, Zwykle identyfikator wiersza, więc nie musisz unieważniać całej pamięci podręcznej, tylko element, który się zmienił. W starszych wersjach, gdzie ładunek nie jest obsługiwany, możesz albo dodać unieważnione wpisy do tabeli dziennika z oznaczeniem czasu, którą twoja klasa pomocnicza zapytuje, gdy otrzyma NOTIFY, albo po prostu unieważnić cały bufor.

Twoja klasa pomocnicza jest teraz LISTENna NOTIFY zdarzeniach, które wysyła WYZWALACZ. Gdy otrzyma Zdarzenie NOTIFY, może unieważnić poszczególne wpisy pamięci podręcznej (zobacz poniżej), lub opróżnić całą pamięć podręczną. Możesz słuchać powiadomień z bazy danych za pomocą PgJDBC obsługuje listen / notify. Aby dostać się do implementacji PostgreSQL, będziesz musiał rozpakować dowolny connection pooler zarządzany java.sql.Connection, aby móc przenieść go do org.postgresql.PGConnection i wywołać getNotifications().

Alternatywa dla LISTEN i NOTIFY, Możesz przeszukiwać tabelę dziennika zmian na timerze i mieć WYZWALACZ w tabeli problemów dołączenie zmienionych identyfikatorów wierszy i zmianę znaczników czasu do dziennika zmian stolik. Takie podejście będzie przenośne, z wyjątkiem potrzeby innego wyzwalacza dla każdego typu DB, ale jest nieefektywne i mniej terminowe. Będzie to wymagało częstego nieefektywnego sondażu i nadal ma opóźnienie czasowe, którego nie ma podejście listen/notify. W PostgreSQL możesz użyć tabeli UNLOGGED, aby trochę obniżyć koszty tego podejścia.

Poziomy pamięci podręcznej

EclipseLink / JPA ma kilka poziomów buforowania.

Bufor pierwszego poziomu znajduje się na EntityManager poziom. Jeżeli jednostka jest dołączona do EntityManager przez persist(...), merge(...), find(...), etc, wtedy EntityManager jest wymagane, aby zwrócić tę samą instancję tego podmiotu, gdy jest ponownie dostępna w tej samej sesji, niezależnie od tego, czy Twoja aplikacja nadal ma do niej odniesienia. Ta dołączona instancja nie będzie aktualna, jeśli zawartość bazy danych uległa zmianie.

2. poziom pamięci podręcznej, który jest opcjonalny, znajduje się na EntityManagerFactory poziom i jest bardziej tradycyjnym cache. To nie jest jasne. czy masz włączoną pamięć podręczną 2. poziomu. Sprawdź dzienniki EclipseLink i persistence.xml. Możesz uzyskać dostęp do pamięci podręcznej drugiego poziomu za pomocą EntityManagerFactory.getCache(); zobacz Cache.

@thedayofcondor pokazał, jak spłukać pamięć podręczną drugiego poziomu za pomocą:

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

Ale można również eksmitować poszczególne obiekty za pomocą evict(java.lang.Class cls, java.lang.Object primaryKey) wywołanie:

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

Które możesz wykorzystać ze swojej @Startup @Singleton NOTIFY listener unieważnia tylko te wpisy, które się zmieniły.

Pierwszy poziom pamięć podręczna nie jest tak łatwa, ponieważ jest częścią logiki aplikacji. Będziesz chciał dowiedzieć się, jak działają EntityManager, dołączone i odłączone byty itp. Jedną z opcji jest zawsze używanie oddzielonych encji dla danej tabeli, gdzie używasz nowego EntityManager za każdym razem, gdy pobierasz encję. To pytanie:

Unieważnienie sesji JPA EntityManager

Zawiera przydatne omówienie obsługi unieważniania pamięci podręcznej menedżera encji. Jednak jest mało prawdopodobne, aby EntityManager pamięć podręczna to twój problem, ponieważ RESTful web service jest zwykle realizowany przy użyciu krótkich sesji EntityManager. Problem może wystąpić tylko wtedy, gdy używasz kontekstów rozszerzonej trwałości lub gdy tworzysz własne sesje EntityManager i zarządzasz nimi, a nie używasz container-managed persistence.

 49
Author: Craig Ringer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:17

Możesz albo całkowicie wyłączyć buforowanie (zobacz: http://wiki.eclipse.org/EclipseLink/FAQ/How_to_disable_the_shared_cache%3F ), ale przygotuj się na dość dużą stratę wydajności.

W przeciwnym razie można programowo wykonać Wyczyść pamięć podręczną za pomocą

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

Możesz mapować go do servletu, więc możesz go nazwać zewnętrznie - jest to lepsze, jeśli twoja baza danych jest modyfikowana zewnętrznie bardzo rzadko i chcesz mieć pewność, że JPS odbierze nową wersję

 7
Author: thedayofcondor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-06 22:30:24

Tylko myśl, ale jak otrzymać EntityManager / Session / whatever?

Jeśli zapytałeś obiekt w jednej sesji, zostanie on odłączony w następnej i będziesz musiał scalić go z powrotem do kontekstu trwałości, aby ponownie nim zarządzać.

Próba pracy z wolnostojącymi encjami może skutkować tymi wyjątkami, które nie są zarządzane, powinieneś ponownie odpytywać encję lub spróbować za pomocą merge (lub podobnych metod).

 0
Author: Volker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-06 20:57:47

JPA domyślnie nie wykonuje buforowania. Musisz go wyraźnie skonfigurować. Wierzę, że to efekt uboczny stylu architektonicznego, który wybrałeś: odpoczynek. Myślę, że buforowanie dzieje się na serwerach internetowych, serwerach proxy itp. Proponuję przeczytać to i debugować więcej.

 -4
Author: Aravind R. Yarram,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-06 20:44:25