强制刷新集合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 managed
exception,通常我会在调用.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)
。
你想如何处理这个问题取决于你的用例。 我可以想到两个选项:
- 必须将DAO绑定到事务的生命周期,并且懒惰地初始化
List
。 对DAO.refreshList
后续调用遍历List
和.refresh(status)
。 如果还需要新添加的实体,则应运行Query
并刷新已知的ItemStatus对象。 - 开始新的交易。 听起来像是与@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 managed
exception。
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查询中涉及的表的一列中的数据已更改,但是正在缓存并且查询用于返回相同的过时数据。