23

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

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

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

使用以下查询

@SuppressWarnings("unchecked")
@Override
public List<ItemStatus> 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<ItemStatus> refreshList(){
    itemStatusDAO.refresh(itemList)
}

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

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

更新测试用例:

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

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

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

5 回答 5

29

你在这里有两个不同的问题。让我们先来个简单的。


javax.ejb.EJBTransactionRolledbackException:实体不受管理

List查询返回的对象本身不是a Entity,所以你不能.refresh这样做。事实上,这就是异常所抱怨的。你要求对EntityManager一个根本不知道的对象做某事Entity

如果您想要.refresh一堆东西,请.refresh逐个遍历它们。


刷新 ItemStatus 列表

根据您的问题,您与 Hibernate 的Session级别缓存交互的方式是您所不期望的。从休眠文档

对于附加到特定 Session 的对象(即,在 Session 范围内)... 用于数据库标识的 JVM 标识由 Hibernate 保证。

这样做的影响Query.getResultList()是您不一定要取回数据库的最新状态。

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

这意味着,如果在Query同一事务中两次执行 from 之间,数据库中特定已知实体的某些数据已更改,则第二个 Query不会获取该更改。但是,它会获取一个全新的ItemStatus实例(除非您使用的是查询缓存,我假设您没有使用)。

长话短说:使用 Hibernate,只要您想在单个事务中加载实体,然后从数据库中获取对该实体的其他更改,您必须显式地.refresh(entity).

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

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

一些附加说明

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

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

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

于 2013-03-06T04:38:56.643 回答
1

您必须引用方法返回的实体entityManager.merge(),例如:

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

这样你应该摆脱javax.ejb.EJBTransactionRolledbackException: Entity not managed异常。

更新

也许返回新集合更安全:

public List<T> refreshCollection(List<T> entityCollection)
    {
        List<T> result = new ArrayList<T>();
        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<T> refreshCollection(List<T> entityCollection)
    {
        List<T> result = new ArrayList<T>();
        T mergedEntity;
        for (T entity : entityCollection) {
            getEntityManager().getEntityManagerFactory().getCache().evict(entity.getClass(), entity.getId());
            result.add(getEntityManager().find(entity.getClass(), entity.getId()));
        }
        return result;
    }
于 2013-03-05T11:00:53.593 回答
1

尝试标记它@Transactional

@Override
@org.jboss.seam.annotations.Transactional
public void refreshList(){
    itemList = em.createQuery("...").getResultList();
}
于 2013-03-07T09:07:54.027 回答
1

选项之一 - 为特定查询结果绕过 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上找到许多其他调整和配置技巧

于 2015-02-13T21:33:21.833 回答
0

正确调试后,我发现我团队中的一位开发人员将它注入 DAO 层,因为-

@Repository
public class SomeDAOImpl

    @PersistenceContext(type = PersistenceContextType.EXTENDED)
    private EntityManager entityManager;

因此,为了合并它被缓存并且查询用于返回相同的陈旧数据,即使数据已在本机 sql 查询中涉及的表的列之一中发生更改。

于 2017-11-21T08:50:07.453 回答