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

时间:2012-11-06 20:36:52

标签: java postgresql jpa netbeans glassfish

我有一个PostgreSQL 8.4数据库,其中包含一些表和视图,这些表和视图基本上是连接在某些表上的。我使用NetBeans 7.2(如here所述)来创建从这些视图和表派生的基于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;
        }
    }

...

}

然后我从NetBeans生成的每个doRefresh()方法中调用find。通常发生的事情是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].

之类的东西

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

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

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

4 个答案:

答案 0 :(得分:52)

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

更新Here's another interesting approach, using pgq and a collection of workers for invalidation

无效信令

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

您的帮助程序类现在LISTEN在触发器发送的NOTIFY事件上。当它获得NOTIFY事件时,它可以使各个缓存条目无效(见下文),或刷新整个缓存。您可以使用PgJDBC's listen/notify support侦听来自数据库的通知。您将需要解开任何连接pooler托管java.sql.Connection以获取基础PostgreSQL实现,以便您可以将其强制转换为org.postgresql.PGConnection并在其上调用getNotifications()

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

缓存级别

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

第一级缓存为EntityManager级别。如果实体通过EntityManagerpersist(...)merge(...)等附加到find(...),那么EntityManager需要返回相同的实例当在同一会话中再次访问该实体时,无论您的应用程序是否仍然引用它。如果您的数据库内容已经更改,则此附加实例将不会是最新的。

第二级缓存是可选的,处于EntityManagerFactory级别,是一种更传统的缓存。目前尚不清楚是否启用了二级缓存。检查EclipseLink日志和persistence.xml。您可以使用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侦听器中使用

来仅使那些已更改的条目无效。

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

Invalidating JPA EntityManager session

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

答案 1 :(得分:8)

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

否则,您可以使用

以编程方式执行清除缓存
em.getEntityManagerFactory().getCache().evictAll();

你可以将它映射到一个servlet,这样你就可以在外部调用它 - 如果你的数据库很少在外部进行修改而你只是想确保JPS能够获得新版本,那就更好了

答案 2 :(得分:0)

只是一个想法,但你如何收到你的EntityManager / Session /什么?

如果您在一个会话中查询该实体,它将在下一个会话中分离,您必须将其合并回持久性上下文才能再次进行管理。

尝试使用分离的实体可能会导致那些未管理的异常,您应该重新查询实体,或者您可以尝试使用merge(或类似的方法)。

答案 3 :(得分:-4)

JPA默认不进行任何缓存。您必须明确配置它。我相信它所选择的建筑风格的副作用:REST。我认为缓存是在Web服务器,代理服务器等处进行的。我建议您阅读this并进行更多调试。