我正在使用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));
}
}
}
答案 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)
。
您希望如何处理这一点取决于您的用例。我可以想到两种选择:
List<ItemStatus>
。对DAO.refreshList
的后续调用将遍历List
和.refresh(status)
。如果您还需要新添加的实体,则应运行Query
并还刷新已知的ItemStatus对象。一些额外的说明
讨论了使用查询提示。这就是他们没有工作的原因:
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查询中涉及的表的一列中已更改数据。