it-swarm.com.ru

Как обновить сущности JPA, когда база данных изменяется асинхронно?

У меня есть база данных PostgreSQL 8.4 с некоторыми таблицами и представлениями, которые по сути являются объединениями некоторых таблиц. Я использовал NetBeans 7.2 (как описано здесь ) для создания REST сервисов, основанных на этих представлениях и таблицах, и развернул их на сервере Glassfish 3.1.2.2.

Существует еще один процесс, который асинхронно обновляет содержимое некоторых таблиц, используемых для построения представлений. Я могу напрямую запрашивать представления и таблицы и видеть, что эти изменения произошли правильно. Однако при извлечении из служб на основе REST значения не совпадают со значениями в базе данных. Я предполагаю, что это потому, что JPA кэшировала локальные копии содержимого базы данных на сервере Glassfish, и JPA необходимо обновить связанные сущности.

Я попытался добавить пару методов в класс AbstractFacade, который генерирует NetBeans:

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

...

}

Затем я вызываю doRefresh() из каждого из find методов, которые генерирует NetBeans. Обычно происходит то, что IllegalArgumentsException выбрасывается с указанием чего-то вроде 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].

Поэтому я ищу несколько советов о том, как правильно обновить сущности, связанные с представлениями, чтобы они были актуальными.

Обновление: Оказывается, мое понимание основной проблемы было неверным. Это в некоторой степени связано с другим вопросом, который я отправил ранее , а именно, у представления не было ни одного поля, которое можно было бы использовать в качестве уникального идентификатора. NetBeans потребовал, чтобы я выбрал поле идентификатора, поэтому я просто выбрал одну часть того, что должно было быть составным ключом. Это демонстрировало поведение, при котором все записи с определенным полем идентификатора были идентичны, даже если в базе данных были записи с тем же полем идентификатора, но остальная часть была другой. JPA не пошла дальше, чем посмотрела на то, что я сказал, что это уникальный идентификатор, и просто вытащила первую найденную запись.

Я решил эту проблему, добавив поле уникального идентификатора (так и не смог заставить правильно работать составной ключ).

31
andand

Я рекомендую добавить класс @Startup@Singleton, который устанавливает соединение JDBC с базой данных PostgreSQL и использует LISTEN И NOTIFY для обработки аннулирования кэша.

Update: Вот еще один интересный подход, использующий pgq и набор работников для аннулирования .

Сигнализация недействительности

Добавьте в обновляемую таблицу триггер, который отправляет NOTIFY при каждом обновлении сущности. В PostgreSQL 9.0 и более поздних версиях NOTIFY может содержать полезную нагрузку, обычно идентификатор строки, поэтому вам не нужно аннулировать весь кэш, а только сущность, которая изменилась. В более старых версиях, где полезная нагрузка не поддерживается, вы можете либо добавить недействительные записи в таблицу журнала с метками времени, которую ваш вспомогательный класс запрашивает при получении NOTIFY, либо просто сделать недействительным весь кэш.

Ваш вспомогательный класс теперь LISTENs для событий NOTIFY, которые отправляет триггер. Когда он получает событие NOTIFY, он может сделать недействительными отдельные записи в кэше (см. Ниже) или очистить весь кэш. Вы можете прослушивать уведомления из базы данных с помощью поддержки прослушивания/уведомления PgJDBC . Вам нужно будет развернуть любой управляемый диспетчер соединений Java.sql.Connection, чтобы добраться до базовой реализации PostgreSQL, чтобы можно было привести его к org.postgresql.PGConnection и вызвать getNotifications().

В качестве альтернативы LISTEN и NOTIFY вы можете опрашивать таблицу журнала изменений по таймеру и иметь триггер для таблицы проблем, добавляя измененные идентификаторы строк и метки времени в таблицу журнала изменений. Этот подход будет переносимым, за исключением необходимости отдельного триггера для каждого типа БД, но он неэффективен и менее своевременен. Это потребует частых неэффективных опросов, и все еще будет иметь задержку по времени, которую не делает подход прослушивания/уведомления. В PostgreSQL вы можете использовать таблицу UNLOGGED, чтобы немного снизить затраты на такой подход.

Уровни кэша

EclipseLink/JPA имеет несколько уровней кэширования.

Кэш 1-го уровня находится на уровне EntityManager . Если объект присоединен к EntityManager с помощью 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) call:

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

который вы можете использовать из вашего @Startup@SingletonNOTIFY слушателя, чтобы сделать недействительными только те записи, которые были изменены.

Кэш 1-го уровня не так прост, потому что это часть логики вашего приложения. Вы захотите узнать о том, как работают EntityManager, присоединенные и отключенные объекты и т.д. Один из вариантов - всегда использовать отдельные сущности для рассматриваемой таблицы, где вы используете новую переменную EntityManager при извлечении сущности. Этот вопрос:

Аннулирование сессии JPA EntityManager

полезное обсуждение обработки недействительности кэша менеджера сущностей. Однако маловероятно, что кеш EntityManager является вашей проблемой, поскольку веб-служба RESTful обычно реализуется с использованием коротких сеансов EntityManager. Это может быть проблемой, только если вы используете расширенные контексты постоянства или если вы создаете и управляете своими собственными сеансами EntityManager, а не используете постоянство, управляемое контейнером.

50
Craig Ringer

Вы можете полностью отключить кэширование (см .: http://wiki.Eclipse.org/EclipseLink/FAQ/How_to_disable_the_shared_cache%3F ), но будьте готовы к довольно значительной потере производительности.

В противном случае вы можете программно очистить кэш с помощью 

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

Вы можете сопоставить его с сервлетом, чтобы вы могли вызывать его внешне - это лучше, если ваша база данных изменяется внешне очень редко, и вы просто хотите быть уверены, что JPS подберет новую версию

8
thedayofcondor

Просто мысль, но как вы получаете ваш EntityManager/Session/что угодно? 

Если вы запросили объект в одном сеансе, он будет отключен в следующем, и вам придется объединить его обратно в контекст постоянства, чтобы снова управлять им. 

Попытка работать с отсоединенными сущностями может привести к этим неуправляемым исключениям, вам следует повторно запросить сущность или вы можете попробовать ее с помощью слияния (или аналогичными методами).

0
Volker