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

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

问题描述

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

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



我尝试添加一些方法到AbstractFacade类的NetBeans生成:

  public abstract class AbstractFacade< T> {
私人课程< 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();

$ b $ private void doRefresh(){
if(_refresh){
EntityManager em = getEntityManager();
em.flush(); (EntityType<> entity:em.getMetamodel()。getEntities()){
if(entity.getName()。contains(entityName)){


)尝试{
em.refresh(entity);
//记录成功
}
catch(IllegalArgumentException e){
//记录失败...通常抱怨实体不被管理
}
}
}

_refresh = false;
}
}

...

}

然后,从NetBeans生成的每个 find 方法调用 doRefresh()。通常会发生的是 IllegalArgumentsException 引发了一些类似于的问题。无法刷新不受管理的对象:EntityTypeImpl @ 28524907:MyView [javaType:class org.my. rest.MyView descriptor:RelationalDescriptor(org.my.rest.MyView - > [DatabaseTable(my_view)]),mappings:12]。



更新:

/ b>原来我对底层问题的理解不正确。这有点与我之前发布的另一个问题有关,即该视图没有可用作唯一标识符的单个字段。 NetBeans需要我选择一个ID字段,所以我只选择了本应该是多部分密钥的一部分。这表现出具有特定ID字段的所有记录是相同的行为,即使数据库具有具有相同ID字段但其余部分不同的记录。 JPA没有进一步比看我告诉它的唯一标识符,只是拉了它找到的第一条记录。



我通过添加唯一标识符字段(从未能够获得多部分键正常工作)。

解决方案

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



更新这是另一个有趣的方法,使用pgq和一组工作人员进行失效 h2>

在正在更新的表上添加一个触发器,该触发器发送 NOTIFY whe从未更新实体。在PostgreSQL 9.0及以上版本中,这个 NOTIFY 可以包含一个有效载荷,通常是一个行ID,所以你不必使整个缓存失效,只需要改变实体。在不支持有效负载的旧版本中,您可以将无效条目添加到帮助程序类在其获取 NOTIFY 时查询的时间戳记日志表中,或者仅使无效整个缓存。



您的帮助类现在 LISTEN s NOTIFY 触发器发送的事件。当它得到一个 NOTIFY 事件时,它可以使各个缓存条目失效(见下文)或刷新整个缓存。您可以使用 PgJDBC的收听/通知支持来收听来自数据库的通知。你将需要打开任何连接池来管理 java.sql.Connection 以便到底层的PostgreSQL实现,所以你可以将它转换为 org.postgresql。 PGConnection 并调用 getNotifications()就可以了。



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

缓存级别



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



第一级缓存位于 EntityManager 水平。如果一个实体通过 persist(...) merge()连接到 EntityManager ...) find(...)等,那么 EntityManager 需要在同一会话中再次访问时返回该实体的同一实例,无论您的应用程序是否仍有引用。如果你的数据库内容已经改变,这个连接的实例将不会是最新的。



第二级缓存是可选的,位于 EntityManagerFactory 级别并且是更传统的缓存。您是否启用了二级缓存尚不清楚。检查你的EclipseLink日志和你的 persistence.xml 。你可以通过 EntityManagerFactory.getCache()来访问二级缓存。请参阅 缓存



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

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

但您也可以使用 evict(java.lang.Class cls,java.lang.Object primaryKey) call:

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

您可以从 @Startup @Singleton NOTIFY 侦听器只会使已更改的条目无效。



第一级缓存并不是那么容易,因为它是您应用程序逻辑的一部分。你会想知道 EntityManager ,附加和分离的实体如何工作。一种选择是始终为所讨论的表使用分离的实体,并在您获取实体时使用新的 EntityManager 。这个问题:

使JPA EntityManager会话无效



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

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.

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.

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;
        }
    }

...

}

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.

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).

解决方案

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.

Update: Here's another interesting approach, using pgq and a collection of workers for invalidation.

Invalidation signalling

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.

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.

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.

Cache levels

EclipseLink/JPA has a couple of levels of caching.

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.

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 showed how to flush the 2nd level cache with:

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

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);

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

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:

Invalidating JPA EntityManager session

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天全站免登陆