SEAMをJPA(Seam Managed Persistance Contextとして実装)で使用し、バッキングBeanでエンティティのコレクション(ArrayList)をバッキングBeanにロードします。
別のユーザーが別のセッションでエンティティの1つを変更した場合、これらの変更をセッションのコレクションに伝達するには、メソッド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();
}
_
クエリを再実行すると、これは既に持っているものと同じデータを返すだけです(データベースにアクセスするのではなく、1次キャッシュを使用していると思います)。
_@Override
public List<ItemStatus> refreshList(){
itemStatusDAO.refresh(itemList)
}
_
entityManager.refresh()
を呼び出すと、これはデータベースから更新されますが、これを使用すると_javax.ejb.EJBTransactionRolledbackException: Entity not managed
_例外が発生します。通常、.refresh()を呼び出す前にentityManager.findById(entity.getId)
を使用して確認しますはPCに接続されていますが、エンティティのコレクションを更新しているので、それはできません。
非常に単純な問題のように思えますが、JPA/hibernateにキャッシュをバイパスさせてデータベースをヒットさせる方法はないと信じられませんか?!
テストケースの更新:
私は2つの異なるブラウザ(1と2)を使用して同じWebページをロードし、ItemStatusエンティティの1つのブール属性を更新する1で変更を行い、更新された属性を表示するためにビューを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));
}
}
}
_
ここでは、2つの問題があります。簡単なものから始めましょう。
javax.ejb.EJBTransactionRolledbackException:エンティティが管理されていません
そのクエリによって返されたオブジェクトのList
はitselfEntity
ではないため、_.refresh
_できません。実際、それが例外の不満です。単に既知のEntityManager
ではないオブジェクトで何かをするようEntity
に求めています。
たくさんのことを_.refresh
_したい場合は、それらを繰り返し、_.refresh
_それらを個別に繰り返します。
ItemStatusのリストの更新
HibernateのSession
レベルのキャッシュとやり取りしているのは、あなたの質問からは予期しないことです。 Hibernate docs から:
特定のセッションにアタッチされたオブジェクトの場合(つまり、セッションのスコープ内)...データベースIDのJVM IDはHibernateによって保証されます。
これがQuery.getResultList()
に与える影響は、必ずしもデータベースの最新の状態に戻るとは限らないことです。
実行するQuery
は、実際にそのクエリに一致するエンティティIDのリストを取得しています。 Session
キャッシュに既に存在するIDは既知のエンティティと照合されますが、存在しないIDはデータベースの状態に基づいて入力されます。以前に既知のエンティティではないデータベースからまったく更新されません。
これは、同じトランザクション内からのQuery
の2回の実行の間に、特定の既知のエンティティのデータベースで一部のデータが変更された場合、2番目のクエリがnotその変化を拾います。ただし、新しいItemStatus
インスタンスを取得します( クエリキャッシュ を使用している場合を除きます)。
簡単に言えば、Hibernateでは、単一のトランザクション内でエンティティをロードし、データベースからそのエンティティへの追加の変更を取得する場合は、明示的に.refresh(entity)
する必要があります。
これにどう対処するかは、ユースケースに少し依存します。すぐに考えられる2つのオプション:
List<ItemStatus>
_を遅延初期化する必要があります。その後の_DAO.refreshList
_の呼び出しは、List
および.refresh(status)
を反復処理します。新しく追加されたエンティティも必要な場合は、Query
を実行し、also既知のItemStatusオブジェクトを更新する必要があります。いくつかの追加メモ
クエリヒントの使用に関する議論がありました。動作しなかった理由は次のとおりです。
org.hibernate.cacheable = falseこれは、非常に特定の状況でのみ推奨される query cache を使用している場合にのみ関連します。ただし、使用していても、クエリキャッシュにはデータではなくオブジェクトIDが含まれているため、状況に影響はありません。
org.hibernate.cacheMode = REFRESHこれは、Hibernateの 2次キャッシュ のディレクティブです。 2次キャッシュがオンになっていて、異なるトランザクションから2つのクエリを発行している場合、2番目のクエリで古いデータが取得され、このディレクティブは問題を解決します。ただし、2つのクエリで同じセッションにいる場合、この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
例外。
[〜#〜] update [〜#〜]
新しいコレクションを返す方が安全かもしれません:
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;
}
オプションの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
マークしてみてください@Transactional
@Override
@org.jboss.seam.annotations.Transactional
public void refreshList(){
itemList = em.createQuery("...").getResultList();
}
適切にデバッグした後、私のチームの開発者の1人がそれをDAOレイヤーに挿入したことに気付きました。
@Repository
public class SomeDAOImpl
@PersistenceContext(type = PersistenceContextType.EXTENDED)
private EntityManager entityManager;
そのため、ネイティブSQLクエリに含まれるテーブルの列の1つでデータが変更された場合でも、統合するためにキャッシュが取得され、クエリは同じ古いデータを返します。