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

<p>у меня есть база данных postgresql 8.4 с некоторыми таблицами и представлениями, которые по сути являются объединениями некоторых таблиц. я использовал netbeans 7.2 (как описано <a href="http://netbeans.org/kb/70/websvc/rest.html">здесь</a> ) для создания rest сервисов, основанных на этих представлениях и таблицах, и развернул их на сервере glassfish 3.1.2.2.</p><p>существует еще один процесс, который асинхронно обновляет содержимое некоторых таблиц, используемых для построения представлений. я могу напрямую запрашивать представления и таблицы и видеть, что эти изменения произошли правильно. однако при извлечении из служб на основе rest значения не совпадают со значениями в базе данных. я предполагаю, что это потому, что jpa кэшировала локальные копии содержимого базы данных на сервере glassfish, и jpa необходимо обновить связанные сущности.</p><p>я попытался добавить пару методов в класс abstractfacade, который генерирует netbeans:</p><pre><code>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; } } ... }</code></pre><p>затем я вызываю <code>dorefresh()</code> из каждого из <code>find</code> методов, которые генерирует netbeans. обычно происходит то, что <code>illegalargumentsexception</code> выбрасывается с указанием чего-то вроде <code>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].</code></p><p>поэтому я ищу несколько советов о том, как правильно обновить сущности, связанные с представлениями, чтобы они были актуальными.</p><p><b>обновление:</b> оказывается, мое понимание основной проблемы было неверным. это в некоторой степени связано с <a href="https://stackoverflow.com/questions/12980332/glassfish-postgresql-table-with-composite-two-part-primary-key-generating-co">другим вопросом, который я отправил ранее</a> , а именно, у представления не было ни одного поля, которое можно было бы использовать в качестве уникального идентификатора. netbeans потребовал, чтобы я выбрал поле идентификатора, поэтому я просто выбрал одну часть того, что должно было быть составным ключом. это демонстрировало поведение, при котором все записи с определенным полем идентификатора были идентичны, даже если в базе данных были записи с тем же полем идентификатора, но остальная часть была другой. jpa не пошла дальше, чем посмотрела на то, что я сказал, что это уникальный идентификатор, и просто вытащила первую найденную запись.</p><p>я решил эту проблему, добавив поле уникального идентификатора (так и не смог заставить правильно работать составной ключ).</p>

<p>я рекомендую добавить класс <code>@startup</code> <code>@singleton</code>, который устанавливает соединение jdbc с базой данных postgresql и использует <a href="http://www.postgresql.org/docs/current/static/sql-notify.html"><code>listen</code> и <code>notify</code></a> для обработки аннулирования кэша.</p><p><strong>update</strong>: <a href="http://momjian.us/main/blogs/pgblog/2014.html#march\_1\_2014">вот еще один интересный подход, использующий pgq и набор работников для аннулирования</a> .</p><h2>сигнализация недействительности</h2><p>добавьте в обновляемую таблицу триггер, который отправляет <code>notify</code> при каждом обновлении сущности. в postgresql 9.0 и более поздних версиях <code>notify</code> может содержать полезную нагрузку, обычно идентификатор строки, поэтому вам не нужно аннулировать весь кэш, а только сущность, которая изменилась. в более старых версиях, где полезная нагрузка не поддерживается, вы можете либо добавить недействительные записи в таблицу журнала с метками времени, которую ваш вспомогательный класс запрашивает при получении <code>notify</code>, либо просто сделать недействительным весь кэш.</p><p>ваш вспомогательный класс теперь <code>listen</code>s для событий <code>notify</code>, которые отправляет триггер. когда он получает событие <code>notify</code>, он может сделать недействительными отдельные записи в кэше (см. ниже) или очистить весь кэш. вы можете прослушивать уведомления из базы данных с помощью <a href="http://jdbc.postgresql.org/documentation/91/listennotify.html">поддержки прослушивания/уведомления pgjdbc</a> . вам нужно будет развернуть любой управляемый диспетчер соединений <code>java.sql.connection</code>, чтобы добраться до базовой реализации postgresql, чтобы можно было привести его к <code>org.postgresql.pgconnection</code> и вызвать <code>getnotifications()</code>.</p><p>в качестве альтернативы <code>listen</code> и <code>notify</code> вы можете опрашивать таблицу журнала изменений по таймеру и иметь триггер для таблицы проблем, добавляя измененные идентификаторы строк и метки времени в таблицу журнала изменений. этот подход будет переносимым, за исключением необходимости отдельного триггера для каждого типа бд, но он неэффективен и менее своевременен. это потребует частых неэффективных опросов, и все еще будет иметь задержку по времени, которую не делает подход прослушивания/уведомления. в postgresql вы можете использовать таблицу <code>unlogged</code>, чтобы немного снизить затраты на такой подход.</p><h2>уровни кэша</h2><p>eclipselink/jpa имеет несколько уровней кэширования.</p><p>кэш 1-го уровня находится на уровне <a href="http://docs.oracle.com/javaee/6/api/javax/persistence/entitymanager.html"><code>entitymanager</code></a> . если объект присоединен к <code>entitymanager</code> с помощью <code>persist(...)</code>, <code>merge(...)</code>, <code>find(...)</code> и т.д., тогда <code>entitymanager</code> должен возвращать <em>тот же экземпляр этого объекта</em> при повторном доступе к нему в том же сеансе, независимо от того, приложение до сих пор имеет ссылки на него. этот прикрепленный экземпляр не будет обновлен, если с тех пор содержимое вашей базы данных изменилось.</p><p>кэш 2-го уровня, который является необязательным, находится на уровне <a href="http://docs.oracle.com/javaee/6/api/javax/persistence/entitymanager.html"><code>entitymanagerfactory</code></a> и является более традиционным кешем. не ясно, включен ли кэш 2-го уровня. проверьте ваши журналы eclipselink и ваш <code>persistence.xml</code>. вы можете получить доступ к кэшу 2-го уровня с помощью <code>entitymanagerfactory.getcache()</code>; смотрите <a href="http://docs.oracle.com/javaee/6/api/javax/persistence/cache.html"><code>cache</code></a> .</p><p>@thedayofcondor показал, как очистить кэш 2-го уровня с помощью:</p><pre><code>em.getentitymanagerfactory().getcache().evictall(); </code></pre><p>но вы также можете выселить отдельные объекты с помощью <a href="http://docs.oracle.com/javaee/6/api/javax/persistence/cache.html"><code>evict(java.lang.class cls, java.lang.object primarykey)</code></a> call:</p><pre><code>em.getentitymanagerfactory().getcache().evict(theclass, theprimarykey); </code></pre><p>который вы можете использовать из вашего <code>@startup</code> <code>@singleton</code> <code>notify</code> слушателя, чтобы сделать недействительными только те записи, которые были изменены.</p><p>кэш 1-го уровня не так прост, потому что это часть логики вашего приложения. вы захотите узнать о том, как работают <code>entitymanager</code>, присоединенные и отключенные объекты и т.д. один из вариантов - всегда использовать отдельные сущности для рассматриваемой таблицы, где вы используете новую переменную <code>entitymanager</code> при извлечении сущности. этот вопрос:</p><p> <a href="https://stackoverflow.com/q/2400430/398670">аннулирование сессии jpa entitymanager</a> </p><p>полезное обсуждение обработки недействительности кэша менеджера сущностей. однако маловероятно, что кеш <code>entitymanager</code> является вашей проблемой, поскольку веб-служба restful обычно реализуется с использованием коротких сеансов <code>entitymanager</code>. это может быть проблемой, только если вы используете расширенные контексты постоянства или если вы создаете и управляете своими собственными сеансами <code>entitymanager</code>, а не используете постоянство, управляемое контейнером.</p>

<p>вы можете полностью отключить кэширование (см .: <a href="http://wiki.eclipse.org/eclipselink/faq/how\_to\_disable\_the\_shared\_cache%3f">http://wiki.eclipse.org/eclipselink/faq/how\_to\_disable\_the\_shared\_cache%3f</a> ), но будьте готовы к довольно значительной потере производительности.</p><p>в противном случае вы можете программно очистить кэш с помощью </p><pre><code>em.getentitymanagerfactory().getcache().evictall(); </code></pre><p>вы можете сопоставить его с сервлетом, чтобы вы могли вызывать его внешне - это лучше, если ваша база данных изменяется внешне очень редко, и вы просто хотите быть уверены, что jps подберет новую версию</p>

<p>просто мысль, но как вы получаете ваш entitymanager/session/что угодно? </p><p>если вы запросили объект в одном сеансе, он будет отключен в следующем, и вам придется объединить его обратно в контекст постоянства, чтобы снова управлять им. </p><p>попытка работать с отсоединенными сущностями может привести к этим неуправляемым исключениям, вам следует повторно запросить сущность или вы можете попробовать ее с помощью слияния (или аналогичными методами).</p>