代码之家  ›  专栏  ›  技术社区  ›  DaveB

强制刷新集合JPA entityManager

  •  22
  • DaveB  · 技术社区  · 11 年前

    我将SEAM与JPA一起使用(实现为SEAM Managed Persistence 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));
            }
        }
    }
    
    5 回复  |  直到 11 年前
        1
  •  29
  •   sharakan    11 年前

    你有两个不同的问题。让我们先简单一点。


    javax.ejb.EJBTransactionRolledback异常:未管理实体

    这个 List 该查询返回的对象的数目不是 它本身 Entity ,所以你不能 .refresh 事实上,这就是例外所抱怨的。你在问 EntityManager 用一个根本不为人所知的物体做某事 实体 .

    如果你想 刷新 一堆东西,迭代它们 刷新 他们各自。


    正在刷新ItemStatus列表

    您正在与Hibernate的交互 Session -级别缓存的方式,从你的问题中,你不会想到。来自 Hibernate docs :

    对于附加到特定会话的对象(即,在 会话)。。。数据库标识的JVM标识由 冬眠

    这对 Query.getResultList() 就是您不一定要恢复数据库的最新状态。

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

    这意味着,在两次执行 查询 在同一事务中,数据库中某个特定已知实体的某些数据发生了更改,第二个查询将 拿起零钱。然而,它会选择一个全新的 ItemStatus 实例(除非您使用 query cache ,我想你不是)。

    长话短说:使用Hibernate,无论何时您想在单个事务中加载一个实体,然后从数据库中获取对该实体的其他更改,都必须明确 .refresh(entity) .

    您希望如何处理这一问题在一定程度上取决于您的用例。我可以马上想到两个选项:

    1. Have是将DAO绑定到事务的寿命,并惰性地初始化 List<ItemStatus> 。对的后续调用 DAO.refreshList 遍历 列表 .refresh(status) 。如果您还需要新添加的实体,则应运行 查询 而且 刷新已知的ItemStatus对象。
    2. 启动一个新事务。从你与@Perception的聊天中听起来,这不是一个选项。

    一些附加说明

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

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

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

        2
  •  1
  •   Zaitsev    9 年前

    选项之一-为特定查询结果绕过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

        3
  •  0
  •   Ondrej Bozek    11 年前

    您必须引用由返回的实体 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;
        }
    
        4
  •  0
  •   Tair    11 年前

    试着做标记 @Transactional

    @Override
    @org.jboss.seam.annotations.Transactional
    public void refreshList(){
        itemList = em.createQuery("...").getResultList();
    }
    
        5
  •  0
  •   Farhan stands with Palestine    7 年前

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

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

    因此,为了进行合并,它被缓存,查询使用返回相同的陈旧数据,即使本机sql查询中涉及的表的某一列中的数据已经更改。