JPA/Hibernateアプリケーションにいくつかのマップされたオブジェクトがあります。ネットワーク上で、これらのオブジェクトの更新を表すパケット、または実際には新しいオブジェクト全体を表すパケットを受信します。
次のようなメソッドを記述したい
<T> T getOrCreate(Class<T> klass, Object primaryKey)
pk primaryKeyを使用してデータベースにオブジェクトが存在する場合は、指定されたクラスのオブジェクトを返し、そうでない場合は、そのクラスの新しいオブジェクトを作成して永続化して返します。
次にオブジェクトで行うことは、トランザクション内ですべてのフィールドを更新することです。
JPAでこれを行う慣用的な方法はありますか、または私の問題を解決するより良い方法はありますか?
<T> T getOrCreate(Class<T> klass, Object primaryKey)
のようなメソッドを書きたい
これは簡単ではありません。
素朴なアプローチは次のようなことです(メソッドがトランザクション内で実行されていると仮定します)。
public <T> T findOrCreate(Class<T> entityClass, Object primaryKey) {
T entity = em.find(entityClass, primaryKey);
if ( entity != null ) {
return entity;
} else {
try {
entity = entityClass.newInstance();
/* use more reflection to set the pk (probably need a base entity) */
return entity;
} catch ( Exception e ) {
throw new RuntimeException(e);
}
}
}
ただし、並行環境では、このコードは競合状態のために失敗する可能性があります。
T1:BEGIN TX; T2:BEGIN TX; T1:SELECT w/id = 123; //null T2を返します:SELECT w/id = 123; // nullを返します T1:INSERT w/id = 123; T1:COMMIT; //行が挿入されました T2:INSERT w/name = 123; T2:COMMIT; //制約違反
また、複数のJVMを実行している場合、同期は役に立ちません。テーブルロックを取得しないと(これはかなり恐ろしいことです)、これを解決する方法がわかりません。
そのような場合、最初に体系的に挿入し、可能な例外を処理して後続の選択を(新しいトランザクションで)実行する方が良いのではないかと思います。
上記の制約に関する詳細を追加する必要があります(マルチスレッド?分散環境?)。
純粋なJPAを使用すると、ネストされたエンティティマネージャーを備えたマルチスレッドソリューションでこれを楽観的に解決できます(実際には、ネストされたトランザクションが必要ですが、純粋なJPAでは可能ではないと思います)。基本的に、検索または作成操作をカプセル化するマイクロトランザクションを作成する必要があります。このパフォーマンスは素晴らしいものではなく、大規模な一括作成には適していませんが、ほとんどの場合十分です。
前提条件:
Finder
と呼びます。factory
と呼びます。コード:
public <T> T findOrCreate(Supplier<T> Finder, Supplier<T> factory) {
EntityManager innerEntityManager = entityManagerFactory.createEntityManager();
innerEntityManager.getTransaction().begin();
try {
//Try the naive find-or-create in our inner entity manager
if(Finder.get() == null) {
T newInstance = factory.get();
innerEntityManager.persist(newInstance);
}
innerEntityManager.getTransaction().commit();
} catch (PersistenceException ex) {
//This may be a unique constraint violation or it could be some
//other issue. We will attempt to determine which it is by trying
//to find the entity. Either way, our attempt failed and we
//roll back the tx.
innerEntityManager.getTransaction().rollback();
T entity = Finder.get();
if(entity == null) {
//Must have been some other issue
throw ex;
} else {
//Either it was a unique constraint violation or we don't
//care because someone else has succeeded
return entity;
}
} catch (Throwable t) {
innerEntityManager.getTransaction().rollback();
throw t;
} finally {
innerEntityManager.close();
}
//If we didn't hit an exception then we successfully created it
//in the inner transaction. We now need to find the entity in
//our outer transaction.
return Finder.get();
}