强制刷新集合 JPA 实体管理器

2022-09-01 23:13:04

我将SEAM与JPA一起使用(作为Seam托管持久化上下文实现),在我的支持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();
}

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

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

调用 ,这应该从数据库刷新,但是当我使用它时,我得到一个例外,通常我会在调用.refresh()之前使用,以确保它连接到PC,但是由于我正在刷新实体集合,因此我无法做到这一点。entityManager.refresh()javax.ejb.EJBTransactionRolledbackException: Entity not managedentityManager.findById(entity.getId)

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

更新测试用例:

我正在使用两个不同的浏览器(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));
        }
    }
}

答案 1

你在这里遇到了两个不同的问题。让我们先来看看简单的一个。


javax.ejb.EJBTransactionRolledbackException: Entity not managed

该查询返回的对象本身不是 一个 ,所以你不能。事实上,这就是例外所抱怨的。您要求 对根本不是未知的对象执行某些操作。ListEntity.refreshEntityManagerEntity

如果你想做一堆事情,请分别迭代它们和它们。.refresh.refresh


刷新 ItemStatus 列表

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

对于附加到特定会话的对象(即,在会话范围内)...数据库身份的 JVM 身份由 Hibernate 保证。

这样做的影响是,您不一定能恢复数据库的最新状态。Query.getResultList()

您运行的实际上是获取与该查询匹配的实体 ID 的列表。缓存中已存在的任何 ID 最多与已知实体匹配,而未匹配的任何 ID 则基于数据库状态进行填充。以前已知的实体根本不会从数据库中刷新。QuerySession

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

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

您希望如何处理此问题取决于您的用例。我可以立即想到两个选项:

  1. 有的是将DAO绑定到交易的生命周期,并懒惰地初始化.循环访问 和 的后续调用。如果还需要新添加的实体,则应运行并刷新已知的 ItemStatus 对象。List<ItemStatus>DAO.refreshListList.refresh(status)Query
  2. 开始新的交易。听起来像是你与@Perception的聊天,尽管这不是一个选择。

一些额外的注意事项

讨论了如何使用查询提示。以下是它们不起作用的原因:

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

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


答案 2

您必须引用通过方法返回的实体,如下所示: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;
    }

推荐