后端数据库异步变化时如何刷新JPA实体?

2022-09-01 04:28:31

我有一个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;
        }
    }

...

}

然后,我从 NetBeans 生成的每个方法中调用。通常发生的事情是被抛出,说明一些像doRefresh()findIllegalArgumentsExceptionCan 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没有走得更远,只是看着我告诉它是唯一标识符的东西,并简单地提取了它找到的第一条记录。

我通过添加唯一标识符字段解决了这个问题(永远无法使多部分密钥正常工作)。


答案 1

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

更新这是另一种有趣的方法,使用pgq和一组工作线程进行失效

失效信号

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

您的帮助程序类现在位于触发器发送的事件上。当它获得事件时,它可能会使单个缓存条目失效(见下文),或刷新整个缓存。您可以使用 PgJDBC 的侦听/通知支持来侦听来自数据库的通知。您需要解开任何管理的连接池器的包装,以访问底层的PostgreSQL实现,以便您可以将其转换为并调用它。LISTENNOTIFYNOTIFYjava.sql.Connectionorg.postgresql.PGConnectiongetNotifications()

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

缓存级别

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

第一级缓存位于实体管理器级别。如果将某个实体附加到 by 、 、 等,则无论应用程序是否仍具有对它的引用,都需要在同一会话中再次访问该实体时返回该实体的同一实例。如果您的数据库内容已更改,则此附加的实例将不是最新的。EntityManagerpersist(...)merge(...)find(...)EntityManager

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

@thedayofcondor展示了如何使用以下命令刷新第二级缓存:

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

但你也可以使用evict(java.lang.Class cls, java.lang.Object primaryKey)调用来逐出单个对象:

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

您可以从侦听器使用它来使那些已更改的条目无效。@Startup@SingletonNOTIFY

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

使 JPA 实体管理器会话失效

对处理实体管理器的缓存失效进行了有用的讨论。但是,缓存不太可能是您的问题,因为 RESTful Web 服务通常是使用短会话实现的。仅当您使用扩展持久性上下文,或者您正在创建和管理自己的会话而不是使用容器管理的持久性时,这才可能是一个问题。EntityManagerEntityManagerEntityManager


答案 2

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

否则,您可以使用

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

你可以把它映射到一个servlet,这样你就可以在外部调用它 - 如果你的数据库很少在外部修改,你只是想确保JPS会选择新版本,那就更好了。


推荐