如何在后端数据库异步更改时刷新JPA实体?

and*_*and 35 java postgresql netbeans jpa glassfish

我有一个PostgreSQL 8.4数据库,其中包含一些表和视图,这些表和视图基本上是连接在某些表上的.我使用的NetBeans 7.2(如描述这里)来从这些观点和表中派生的基于REST的服务,并部署那些到Glassfish的3.1.2.2服务器.

还有另一个进程异步更新用于构建视图的一些表中的内容.我可以直接查询视图和表格,看看这些变化是否正确发生.但是,从基于REST的服务中提取时,这些值与数据库中的值不同.我假设这是因为JPA已经缓存了Glassfish服务器上的数据库内容的本地副本,JPA需要刷新关联的实体.

我尝试将一些方法添加到NetBeans生成的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;
        }
    }

...

}
Run Code Online (Sandbox Code Playgroud)

然后doRefresh(),我从findNetBeans生成的每个方法调用.通常会发生的事情是IllegalArgumentsException抛出一些类似的东西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].

所以我正在寻找一些关于如何正确刷新与视图相关的实体的建议,以便它是最新的.

更新:结果我对底层问题的理解不正确.它与我之前发布的另一个问题有些相关,即视图没有可以用作唯一标识符的单个字段.NetBeans需要我选择一个ID字段,所以我只选择了应该是多部分密钥的一部分.这表现出具有特定ID字段的所有记录都相同的行为,即使数据库具有相同ID字段但其余部分不同的记录.JPA没有做任何进一步的事情,只看我说的是唯一标识符并简单地拉出它找到的第一条记录.

我通过添加唯一标识符字段解决了这个问题(从来没有能够使多部分键正常工作).

Cra*_*ger 52

我建议添加一个@Startup @Singleton类,该类建立与PostgreSQL数据库的JDBC连接,并使用LISTENNOTIFY处理缓存失效.

更新:这是另一个有趣的方法,使用pgq和一组工人进行无效.

无效信令

在正在更新的表上添加一个触发器,NOTIFY每当实体更新时发送该触发器.在PostgreSQL 9.0及更高版本上,它NOTIFY可以包含有效负载,通常是行ID,因此您不必使整个缓存无效,只需更改已更改的实体.在不支持有效负载的旧版本中,您可以将无效的条目添加到助手类在获取时查询的时间戳记日志表中NOTIFY,或者只是使整个缓存无效.

您的助手类现在处于触发器发送LISTENNOTIFY事件上.当它获得NOTIFY事件时,它可以使各个缓存条目无效(见下文),或刷新整个缓存.您可以使用PgJDBC的listen/notify支持从数据库中侦听通知.您将需要解包任何连接的pooler托管java.sql.Connection以获取底层的PostgreSQL实现,以便您可以将其强制转换org.postgresql.PGConnection并调用getNotifications()它.

一种替代LISTENNOTIFY,你可以轮询更改日志表上的计时器,并且对问题表追加改变行ID触发并更改时间戳更改日志表.除了每种数据库类型需要不同的触发器之外,这种方法是可移植的,但效率低且不太及时.它需要频繁的低效轮询,并且仍然存在监听/通知方法没有的时间延迟.在PostgreSQL中,您可以使用UNLOGGED表来降低此方法的成本.

缓存级别

EclipseLink/JPA有几个级别的缓存.

第一级缓存位于该EntityManager级别.如果实体附加到EntityManager通过persist(...),merge(...),find(...),等,则EntityManager需要返回该实体的同一实例时,它是在同一会话内再次访问,不管是不是你的应用程序仍然有它的引用.如果您的数据库内容已更改,则此附加实例将不是最新的.

第二级缓存是可选的,位于EntityManagerFactory级别,是更传统的缓存.目前尚不清楚是否启用了二级缓存.检查您的EclipseLink日志和您的persistence.xml.您可以访问二级缓存EntityManagerFactory.getCache(); 看Cache.

@thedayofcondor展示了如何使用以下方式刷新二级缓存:

em.getEntityManagerFactory().getCache().evictAll();
Run Code Online (Sandbox Code Playgroud)

但您也可以通过evict(java.lang.Class cls, java.lang.Object primaryKey)调用逐出个别对象:

em.getEntityManagerFactory().getCache().evict(theClass, thePrimaryKey);
Run Code Online (Sandbox Code Playgroud)

您可以从@Startup @Singleton NOTIFY侦听器中使用它来仅使那些已更改的条目无效.

第一级缓存并不那么容易,因为它是应用程序逻辑的一部分.您将需要了解EntityManager,附加和分离的实体等是如何工作的.一种选择是始终为相关表使用分离的实体,EntityManager每当您获取实体时都使用新的实体.这个问题:

使JPA EntityManager会话无效

有一个有用的讨论处理实体管理器的缓存失效.但是,EntityManager缓存不太可能是您的问题,因为RESTful Web服务通常使用短EntityManager会话实现.如果您正在使用扩展持久性上下文,或者您正在创建和管理自己的EntityManager会话而不是使用容器管理的持久性,则这可能只是一个问题.

  • 事实证明,我问的问题没有正确反映我面临的根本问题(请参阅OP中的更新)。然而,这无疑是对我所问问题的最好解释。 (2认同)

the*_*dor 8

您可以完全禁用缓存(请参阅:http://wiki.eclipse.org/EclipseLink/FAQ/How_to_disable_the_shared_cache%3F),但要做好相当大的性能损失准备.

否则,您可以使用编程方式执行清除缓存

em.getEntityManagerFactory().getCache().evictAll();
Run Code Online (Sandbox Code Playgroud)

您可以将它映射到servlet,以便可以在外部调用它 - 如果您的数据库很少在外部进行修改,那么这样做会更好,而您只是想确保JPS能够获取新版本