后端数据库异步更改时如何刷新JPA实体? [英] How to refresh JPA entities when backend database changes asynchronously?

查看:28
本文介绍了后端数据库异步更改时如何刷新JPA实体?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 PostgreSQL 8.4 数据库,其中包含一些表和视图,这些表和视图本质上是连接到一些表上的.我使用 NetBeans 7.2(如此处所述)来创建从这些衍生的基于 REST 的服务视图和表并将它们部署到 Glassfish 3.1.2.2 服务器.

I have a PostgreSQL 8.4 database with some tables and views which are essentially joins on some of the tables. I used NetBeans 7.2 (as described here) to create REST based services derived from those views and tables and deployed those to a Glassfish 3.1.2.2 server.

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

There is another process which asynchronously updates contents in some of tables used to build the views. I can directly query the views and tables and see these changes have occured correctly. However, when pulled from the REST based services, the values are not the same as those in the database. I am assuming this is because JPA has cached local copies of the database contents on the Glassfish server and JPA needs to refresh the associated entities.

我尝试向 NetBeans 生成的 AbstractFacade 类添加几个方法:

I have tried adding a couple of methods to the AbstractFacade class NetBeans generates:

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 生成的每个 find 方法中调用 doRefresh().通常发生的是 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].

I then call doRefresh() from each of the find methods NetBeans generates. What normally happens is the IllegalArgumentsException is thrown stating somethng like 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].

因此,我正在寻找有关如何正确刷新与视图关联的实体以使其保持最新的一些建议.

So I'm looking for some suggestions on how to correctly refresh the entities associated with the views so it is up to date.

更新: 原来我对潜在问题的理解是不正确的.它与 我之前发布的另一个问题有些相关,即视图没有可以用作唯一标识符的单个字段.NetBeans 要求我选择一个 ID 字段,所以我只选择了应该是多部分键的一部分.这表现出具有特定 ID 字段的所有记录都相同的行为,即使数据库具有具有相同 ID 字段的记录但其余记录不同.JPA 只是查看我告诉它的唯一标识符并简单地提取它找到的第一条记录.

UPDATE: Turns out my understanding of the underlying problem was not correct. It is somewhat related to another question I posted earlier, namely the view had no single field which could be used as a unique identifier. NetBeans required I select an ID field, so I just chose one part of what should have been a multi-part key. This exhibited the behavior that all records with a particular ID field were identical, even though the database had records with the same ID field but the rest of it was different. JPA didn't go any further than looking at what I told it was the unique identifier and simply pulled the first record it found.

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

I resolved this by adding a unique identifier field (never was able to get the multipart key to work properly).

推荐答案

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

I recommend adding an @Startup @Singleton class that establishes a JDBC connection to the PostgreSQL database and uses LISTEN and NOTIFY to handle cache invalidation.

更新:这是另一种有趣的方法,使用 pgq以及一组用于失效的工作器.

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

Add a trigger on the table that's being updated that sends a NOTIFY whenever an entity is updated. On PostgreSQL 9.0 and above this NOTIFY can contain a payload, usually a row ID, so you don't have to invalidate your entire cache, just the entity that has changed. On older versions where a payload isn't supported you can either add the invalidated entries to a timestamped log table that your helper class queries when it gets a NOTIFY, or just invalidate the whole cache.

您的助手类现在LISTENs 对触发器发送的NOTIFY 事件进行处理.当它收到 NOTIFY 事件时,它可以使单个缓存条目无效(见下文),或刷新整个缓存.您可以使用PgJDBC 的侦听/通知支持 来侦听来自数据库的通知.您将需要解开任何由 java.sql.Connection 管理的连接池以获取底层 PostgreSQL 实现,以便您可以将其转换为 org.postgresql.PGConnection 并调用 getNotifications() 就可以了.

Your helper class now LISTENs on the NOTIFY events the trigger sends. When it gets a NOTIFY event, it can invalidate individual cache entries (see below), or flush the entire cache. You can listen for notifications from the database with PgJDBC's listen/notify support. You will need to unwrap any connection pooler managed java.sql.Connection to get to the underlying PostgreSQL implementation so you can cast it to org.postgresql.PGConnection and call getNotifications() on it.

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

An an alternative to LISTEN and NOTIFY, you could poll a change log table on a timer, and have a trigger on the problem table append changed row IDs and change timestamps to the change log table. This approach will be portable except for the need for a different trigger for each DB type, but it's inefficient and less timely. It'll require frequent inefficient polling, and still have a time delay that the listen/notify approach does not. In PostgreSQL you can use an UNLOGGED table to reduce the costs of this approach a little bit.

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

EclipseLink/JPA has a couple of levels of caching.

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

The 1st level cache is at the EntityManager level. If an entity is attached to an EntityManager by persist(...), merge(...), find(...), etc, then the EntityManager is required to return the same instance of that entity when it is accessed again within the same session, whether or not your application still has references to it. This attached instance won't be up-to-date if your database contents have since changed.

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

The 2nd level cache, which is optional, is at the EntityManagerFactory level and is a more traditional cache. It isn't clear whether you have the 2nd level cache enabled. Check your EclipseLink logs and your persistence.xml. You can get access to the 2nd level cache with EntityManagerFactory.getCache(); see Cache.

@thedayofcondor 展示了如何刷新二级缓存:

@thedayofcondor showed how to flush the 2nd level cache with:

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

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

but you can also evict individual objects with the evict(java.lang.Class cls, java.lang.Object primaryKey) call:

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

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

which you can use from your @Startup @Singleton NOTIFY listener to invalidate only those entries that have changed.

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

The 1st level cache isn't so easy, because it's part of your application logic. You'll want to learn about how the EntityManager, attached and detached entities, etc work. One option is to always use detached entities for the table in question, where you use a new EntityManager whenever you fetch the entity. This question:

使 JPA EntityManager 会话无效

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

has a useful discussion of handling invalidation of the entity manager's cache. However, it's unlikely that an EntityManager cache is your problem, because a RESTful web service is usually implemented using short EntityManager sessions. This is only likely to be an issue if you're using extended persistence contexts, or if you're creating and managing your own EntityManager sessions rather than using container-managed persistence.

这篇关于后端数据库异步更改时如何刷新JPA实体?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆