web-dev-qa-db-ja.com

コレクションJPA entityManagerの強制更新

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));
        }
    }
}
_
23
DaveB

ここでは、2つの問題があります。簡単なものから始めましょう。


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

そのクエリによって返されたオブジェクトのListitselfEntityではないため、_.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つのオプション:

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

いくつかの追加メモ

クエリヒントの使用に関する議論がありました。動作しなかった理由は次のとおりです。

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

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

28
sharakan

次のような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
Ondrej Bozek

オプションの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

1
Zaitsev

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

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

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

@Repository
public class SomeDAOImpl

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

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

0
Shirgill Farhan