Подтвердить что ты не робот

Как обновить объекты 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 требуется, чтобы я выбрал поле ID, поэтому я просто выбрал одну часть того, что должно было быть многочастным ключом. Это показало, что все записи с определенным полем идентификатора были идентичны, хотя в базе данных были записи с одинаковым идентификационным полем, но остальная часть была другой. JPA не идет дальше, чем смотреть на то, что я сказал, что это уникальный идентификатор, и просто вытащил первую найденную запись.

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

4b9b3361

Ответ 1

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

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

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

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

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

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

Уровни кеша

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

Кэш 1-го уровня находится на уровне EntityManager. Если объект привязан к EntityManager от persist(...), merge(...), find(...) и т.д., Тогда EntityManager требуется вернуть тот же экземпляр этого объекта, когда к нему снова обращаются в тот же сеанс, имеет ли ваше приложение все еще ссылки на него. Этот прикрепленный экземпляр не будет обновлен, если ваше содержимое базы данных с тех пор изменилось.

Кэш второго уровня, который является необязательным, находится на уровне EntityManagerFactory и является более традиционным кешем. Неясно, включен ли кеш второго уровня. Проверьте журналы EclipseLink и persistence.xml. Вы можете получить доступ к кешу 2-го уровня с помощью EntityManagerFactory.getCache(); см. Cache.

@thedayofcondor показал, как очистить кеш второго уровня:

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

но вы также можете выселить отдельные объекты с помощью evict(java.lang.Class cls, java.lang.Object primaryKey):

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

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

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

Недействительный сеанс JPY EntityManager

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

Ответ 2

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

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

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

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

Ответ 3

Просто мысль, но как вы получаете свой EntityManager/Session/что-то еще?

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

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

Ответ 4

JPA не выполняет кэширование по умолчанию. Вы должны явно настроить его. Я считаю, что это побочный эффект выбранного вами архитектурного стиля: REST. Я думаю, что кэширование происходит на веб-серверах, прокси-серверах и т.д. Я предлагаю вам читать this и отлаживать больше.