强制刷新集合JPA entityManager

时间:2013-02-26 12:29:30

标签: java hibernate jpa seam

我正在使用SEAM和JPA(实现为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异常,通常我会在调用.refresh()之前使用entityManager.findById(entity.getId)来确保它是附在电脑上,但由于我正在刷新一系列实体,我无法做到这一点。

这似乎是一个非常简单的问题,我无法相信没有办法强制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 个答案:

答案 0 :(得分:27)

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


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

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

如果您想要.refresh一堆内容,请逐一浏览它们并.refresh


刷新ItemStatus列表

您正在与Hibernate的Session级缓存进行交互,从您的问题来看,您并不期望这样。来自Hibernate docs

  

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

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

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

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

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

您希望如何处理这一点取决于您的用例。我可以想到两种选择:

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

  3. 一些额外的说明

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

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

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

答案 1 :(得分: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;
    }

答案 2 :(得分:1)

尝试将其标记为@Transactional

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

答案 3 :(得分: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

答案 4 :(得分:0)

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="29dp"
            android:layout_marginTop="16dp"
            android:background="@color/colorAlabaster">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignBaseline="@+id/textView9"
                android:layout_alignBottom="@+id/textView9"
                android:layout_alignParentLeft="true"
                android:layout_alignParentStart="true"
                android:layout_marginLeft="50dp"
                android:gravity="center"
                android:text="Retailer" />

            <TextView
                android:id="@+id/textView9"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerHorizontal="true"
                android:layout_centerInParent="true"
                android:layout_centerVertical="true"
                android:text="Hari sebelumnya" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerHorizontal="true"
                android:layout_centerVertical="true"
                android:layout_marginRight="10dp"
                android:text="Pindahkan ke hari" />
        </RelativeLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:orientation="horizontal">

            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginLeft="8dp"
                android:layout_weight="1">


                <com.hutchison.h3i.newangie.customviews.CircleImageView
                    android:id="@+id/recycle_profile"
                    android:layout_width="@dimen/growth_sell_in_out_icon_size"
                    android:layout_height="@dimen/growth_sell_in_out_icon_size"
                    android:layout_gravity="center"
                    android:layout_marginLeft="16dp"
                    android:src="@drawable/ic_default_profile" />

                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:layout_gravity="center"
                    android:layout_marginLeft="8dp"
                    android:orientation="vertical">

                    <TextView
                        android:id="@+id/recycle_txt_acc_num"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:ellipsize="end"
                        android:maxLines="1"
                        android:singleLine="false"
                        android:text="@string/route_plan_default_number"
                        android:textColor="@color/colorBlack"
                        android:textSize="@dimen/text_size_very_small" />

                    <TextView
                        android:id="@+id/recycle_txt_acc_name"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:ellipsize="end"
                        android:maxLines="1"
                        android:singleLine="false"
                        android:text="@string/route_plan_default_name"
                        android:textColor="@color/colorCyan"
                        android:textSize="@dimen/text_size_very_small" />
                </LinearLayout>

                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_weight="1">

                    <android.support.v7.widget.AppCompatButton
                        android:layout_width="86dp"
                        android:layout_height="29dp"
                        android:layout_gravity="center_horizontal"
                        android:gravity="center"
                        android:layout_marginLeft="8dp"
                        android:background="@drawable/border_grey_curve"
                        android:text="Selasa"
                        android:textAllCaps="false"
                        android:textColor="@color/colorBlack" />

                </LinearLayout>

                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_weight=".2">

                    <ImageView
                        android:layout_width="10dp"
                        android:layout_height="10dp"
                        android:layout_gravity="center"
                        android:layout_marginLeft="10dp"
                        android:background="@drawable/ic_arrow_right"
                        android:gravity="center"
                        android:textColor="@color/colorBlack"
                        android:textSize="16dp"
                        android:textStyle="bold" />
                </LinearLayout>

                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_marginLeft="10dp"
                    android:layout_weight="1">

                    <android.support.v7.widget.AppCompatButton
                        android:layout_width="86dp"
                        android:layout_height="29dp"
                        android:background="@drawable/border_grey_curve"
                        android:drawablePadding="5dp"
                        android:drawableRight="@drawable/ic_arrow_bottom"
                        android:singleLine="true"
                        android:text="Kamis"
                        android:textAllCaps="false"
                        android:textColor="@color/colorBlack"
                        android:textSize="@dimen/text_size_small" />
                </LinearLayout>
            </LinearLayout>
        </LinearLayout>

    </LinearLayout>
</LinearLayout>

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