23

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これを使用すると例外が発生します。通常は、entityManager.findById(entity.getId).refresh()を呼び出す前に使用して、PCに接続されていることを確認しますが、エンティティのコレクションを更新しているため、これを行うことはできません。

非常に単純な問題のようです。JPA/hibernateにキャッシュをバイパスしてデータベースにアクセスさせる方法がないとは信じられません。

テストケースの更新:

2つの異なるブラウザ(1と2)を使用して同じWebページをロードしています。1で変更を加えて、ItemStatusエンティティの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));
        }
    }
}
4

5 に答える 5

29

ここで 2 つの別々の問題が発生しています。最初は簡単なものから始めましょう。


javax.ejb.EJBTransactionRolledbackException: エンティティが管理されていません

そのListクエリによって返されるオブジェクトの はそれ自体ではないEntityため、それはできません.refresh。実際、それは例外が不平を言っていることです。EntityManager単に既知の ではないオブジェクトで何かをするように に依頼していますEntity

.refreshたくさんのことをしたい場合は、.refreshそれらを個別に繰り返します。


ItemStatus のリストを更新しています

Sessionあなたの質問から、あなたが期待していない方法で、Hibernate の -level キャッシュと対話しています。休止状態のドキュメントから:

特定のセッション (つまり、セッションのスコープ内) にアタッチされたオブジェクトの場合... データベース ID の JVM ID は、Hibernate によって保証されます。

これによる影響は、Query.getResultList()必ずしもデータベースの最新の状態に戻るとは限らないことです。

Query実行すると、そのクエリに一致するエンティティ ID のリストが実際に取得されます。キャッシュに既に存在する ID はSession既知のエンティティと照合されますが、存在しない ID はデータベースの状態に基づいて入力されます。以前に認識されていたエンティティ、データベースからまったく更新されません。

これが意味することはQuery、同じトランザクション内の from の 2 回の実行の間に、特定の既知のエンティティのデータベースで一部のデータが変更された場合、2 番目のクエリはその変更を取得しないということです。ただし、まったく新しいItemStatusインスタンスを取得します (クエリ キャッシュを使用していない場合を除きますが、使用していないと思います)。

簡単に言えば、Hibernate では、単一のトランザクション内でエンティティをロードし、データベースからそのエンティティへの追加の変更を取得する場合はいつでも、明示的に.refresh(entity).

これにどのように対処するかは、ユースケースによって少し異なります。すぐに考えられる2つのオプション:

  1. DAO をトランザクションの存続期間に結び付け、遅延して初期化する必要がありList<ItemStatus>ます。およびをDAO.refreshList反復処理する後続の呼び出し。新しく追加されたエンティティも必要な場合は、 を実行し、既知の ItemStatus オブジェクトも更新する必要があります。List.refresh(status)Query
  2. 新しいトランザクションを開始します。@Perception とのチャットのように聞こえますが、それはオプションではありません。

いくつかの追加メモ

クエリ ヒントの使用についての議論がありました。それらが機能しなかった理由は次のとおりです。

org.hibernate.cacheable=falseこれは、非常に特殊な状況でのみ推奨されるquery cacheを使用している場合にのみ関連します。ただし、使用していたとしても、クエリ キャッシュにはデータではなくオブジェクト ID が含まれているため、状況に影響はありません。

org.hibernate.cacheMode=REFRESHこれは、Hibernate の二次キャッシュへのディレクティブです。2 番目のレベルのキャッシュが有効になっていて、異なるトランザクションから 2 つのクエリを発行していた場合、2 番目のクエリで古いデータが取得され、このディレクティブで問題が解決されます。ただし、2 つのクエリで同じセッションにいる場合、2 番目のレベルのキャッシュは、これに新しいエンティティのデータベースの読み込みを回避するためにのみ使用されますSession

于 2013-03-06T04:38:56.643 に答える
1

マークしてみてください@Transactional

@Override
@org.jboss.seam.annotations.Transactional
public void refreshList(){
    itemList = em.createQuery("...").getResultList();
}
于 2013-03-07T09:07:54.027 に答える
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;
    }
于 2013-03-05T11:00:53.593 に答える
0

適切にデバッグした後、私のチームの開発者の 1 人がそれを DAO レイヤーに次のように挿入したことに気付きました。

@Repository
public class SomeDAOImpl

    @PersistenceContext(type = PersistenceContextType.EXTENDED)
    private EntityManager entityManager;

そのため、ネイティブ SQL クエリに関連するテーブルの列の 1 つでデータが変更された場合でも、それがキャッシュされ、クエリが同じ古いデータを返すために使用されていました。

于 2017-11-21T08:50:07.453 に答える