强制刷新集合JPA entityManager

我正在使用SEAM和JPA(实现为Seam Managed Persistance Context),在我的支持bean中,我将一组实体(ArrayList)加载到支持bean中。

如果另一个用户修改了不同会话中的某个实体,我希望将这些更改传播到我的会话中的集合,我有一个方法refreshList()并尝试了以下…

 @Override public List refreshList(){ itemList = itemStatusDAO.getCurrentStatus(); } 

使用以下查询

 @SuppressWarnings("unchecked") @Override public List getCurrentStatus(){ String s = "SELECT DISTINCT iS FROM ItemStatus iS "; s+="ORDER BY iS.dateCreated ASC"; Query q = this.getEntityManager().createQuery(s); return q.getResultList(); } 

重新执行查询,这只返回我已经拥有的相同数据(我假设它使用的是第一级缓存而不是命中数据库)

 @Override public List refreshList(){ itemStatusDAO.refresh(itemList) } 

调用entityManager.refresh() ,这应该从数据库刷新但是我得到一个javax.ejb.EJBTransactionRolledbackException: Entity not managed当我使用它时javax.ejb.EJBTransactionRolledbackException: Entity not managedexception,通常我会在调用.refresh()之前使用entityManager.findById(entity.getId) )确保它连接到PC但我正在刷新一组实体我不能这样做。

这似乎是一个非常简单的问题,我不能相信没有办法强制JPA / hibernate绕过缓存并命中数据库?!

更新测试案例:

我使用两个不同的浏览器(1和2)加载相同的网页,我在1中进行修改,更新其中一个ItemStatus实体的布尔属性,视图刷新为1以显示更新的属性,我检查数据库通过PGAdmin并且行已更新。 然后我按浏览器2中的刷新,该属性尚未更新

我尝试在调用.refresh之前使用以下方法合并所有实体,但实体仍未从数据库更新。

 @Override public void mergeCollectionIntoEntityManager(List entityCollection){ for(T entity: entityCollection){ if(!this.getEntityManager().contains(entity)){ this.getEntityManager().refresh(this.getEntityManager().merge(entity)); } } } 

你在这里有两个不同的问题。 让我们先轻松一下。


javax.ejb.EJBTransactionRolledbackException:未管理的实体

该查询返回的对象List 本身不是Entity ,因此您无法.refresh 。 事实上,这就是exception所抱怨的。 您要求EntityManager对一个不是已知Entity的对象执行某些操作。

如果你想要.refresh一堆东​​西,迭代它们并单独.refresh它们。


刷新ItemStatus列表

您正在以一种从您的问题中不期望的方式与Hibernate的Session -level缓存进行交互。 来自Hibernate文档 :

对于附加到特定会话的对象(即,在会话范围内)… Hibernate保证数据库标识的JVM标识。

这对Query.getResultList()是您不一定回到数据库的最新状态。

您运行的Query实际上是获取与该查询匹配的实体ID列表。 Session高速缓存中已存在的任何ID都与已知实体匹配,而未根据数据库状态填充任何非ID。 根本不会从数据库刷新先前已知的实体。

这意味着,在同一事务中两次执行Query之间,某个已知实体的数据库中某些数据发生了变化的情况下,第二个查询将不会获取该更改。 但是,它会选择一个全新的ItemStatus实例(除非您使用的是查询缓存 ,我认为您不是这样)。

简而言之:对于Hibernate,无论何时您想在一个事务中加载一个实体,然后从数据库中获取对该实体的其他更改,您必须显式.refresh(entity)

你想如何处理这个问题取决于你的用例。 我可以想到两个选项:

  1. 必须将DAO绑定到事务的生命周期,并且懒惰地初始化List 。 对DAO.refreshList后续调用遍历List.refresh(status) 。 如果还需要新添加的实体,则应运行Query并刷新已知的ItemStatus对象。
  2. 开始新的交易。 听起来像是与@Perception聊天,但这不是一个选择。

一些额外的说明

有关于使用查询提示的讨论。 这就是他们没有工作的原因:

org.hibernate.cacheable = false只有在使用查询缓存时才会这样,只有在非常特殊的情况下才会建议使用查询缓存 。 即使您使用它,它也不会影响您的情况,因为查询缓存包含对象ID,而不是数据。

org.hibernate.cacheMode = REFRESH这是Hibernate 二级缓存的指令。 如果打开了二级缓存,并且您发出了来自不同事务的两个查询,那么您将在第二个查询中获得过时数据,并且此指令可以解决问题。 但是,如果您在两个查询中处于同一个Session中,则只会使用二级缓存来避免为此Session新增的实体加载数据库。

你必须使用entityManager.merge()方法返回的实体,类似于:

 @Override public void refreshCollection(List entityCollection){ for(T entity: entityCollection){ if(!this.getEntityManager().contains(entity)){ this.getEntityManager().refresh(this.getEntityManager().merge(entity)); } } } 

这样你就可以摆脱javax.ejb.EJBTransactionRolledbackException: Entity not managedexception。

UPDATE

也许返回新集合更安全:

 public List refreshCollection(List entityCollection) { List result = new ArrayList(); if (entityCollection != null && !entityCollection.isEmpty()) { getEntityManager().getEntityManagerFactory().getCache().evict(entityCollection.get(0).getClass()); T mergedEntity; for (T entity : entityCollection) { mergedEntity = entityManager.merge(entity); getEntityManager().refresh(mergedEntity); result.add(mergedEntity); } } return result; } 

或者,如果您可以访问如下所示的实体ID,则可以更有效:

 public List refreshCollection(List entityCollection) { List result = new ArrayList(); T mergedEntity; for (T entity : entityCollection) { getEntityManager().getEntityManagerFactory().getCache().evict(entity.getClass(), entity.getId()); result.add(getEntityManager().find(entity.getClass(), entity.getId())); } return result; } 

尝试将其标记为@Transactional

 @Override @org.jboss.seam.annotations.Transactional public void refreshList(){ itemList = em.createQuery("...").getResultList(); } 

其中一个选项 – 绕过特定查询的JPA缓存结果:

  // force refresh results and not to use cache query.setHint("javax.persistence.cache.storeMode", "REFRESH"); 

可在此站点上找到许多其他调优和配置技巧http://docs.oracle.com/javaee/6/tutorial/doc/gkjjj.html

在正确调试之后,我发现我团队中的一位开发人员将其注入DAO层 –

 @Repository public class SomeDAOImpl @PersistenceContext(type = PersistenceContextType.EXTENDED) private EntityManager entityManager; 

因此,要合并它,即使在本机sql查询中涉及的表的一列中的数据已更改,但是正在缓存并且查询用于返回相同的过时数据。