web-dev-qa-db-ja.com

JPA EntityManager:merge()よりもpersist()を使用するのはなぜですか?

EntityManager.merge()は新しいオブジェクトを挿入したり既存のオブジェクトを更新することができます。

なぜpersist()(これは新しいオブジェクトしか作成できない)を使いたがるのでしょうか。

900
Aaron Digulla

どちらの方法でもPersistenceContextにエンティティを追加することになります。違いは、後でそのエンティティを使用することです。

Persistはエンティティインスタンスを取得してコンテキストに追加し、そのインスタンスを管理対象にします(つまり、エンティティに対する今後の更新が追跡されます)。

Mergeはエンティティの新しいインスタンスを作成し、指定されたエンティティから状態をコピーして、その新しいコピーを管理対象にします。渡したインスタンスは管理されません(変更を加えてもトランザクションの一部にはなりません - 再度mergeを呼び出さない限り)。

たぶんコード例が役に立つでしょう。

MyEntity e = new MyEntity();

// scenario 1
// tran starts
em.persist(e); 
e.setSomeField(someValue); 
// tran ends, and the row for someField is updated in the database

// scenario 2
// tran starts
e = new MyEntity();
em.merge(e);
e.setSomeField(anotherValue); 
// tran ends but the row for someField is not updated in the database
// (you made the changes *after* merging)

// scenario 3
// tran starts
e = new MyEntity();
MyEntity e2 = em.merge(e);
e2.setSomeField(anotherValue); 
// tran ends and the row for someField is updated
// (the changes were made to e2, not e)

シナリオ1と3はほぼ同等ですが、シナリオ2を使用したい状況がいくつかあります。

1540
Mike

永続化とマージは2つの異なる目的のためのものです(それらはまったく代替手段ではありません)。

(相違情報を拡大するために編集)

固執する:

  • データベースに新しいレジスタを挿入する
  • オブジェクトをエンティティマネージャに接続します。

マージ:

  • 同じIDを持つ添付オブジェクトを見つけて更新します。
  • 存在する場合は、更新してすでに添付されているオブジェクトを返します。
  • 存在しない場合は、データベースに新しいレジスタを挿入します。

persist()効率:

  • データベースに新しいレジスタを挿入する方がmerge()よりも効率的です。
  • 元のオブジェクトと重複しません。

persist()のセマンティクス:

  • それはあなたが誤って挿入していて更新していないことを確認します。

例:

{
    AnyEntity newEntity;
    AnyEntity nonAttachedEntity;
    AnyEntity attachedEntity;

    // Create a new entity and persist it        
    newEntity = new AnyEntity();
    em.persist(newEntity);

    // Save 1 to the database at next flush
    newEntity.setValue(1);

    // Create a new entity with the same Id than the persisted one.
    AnyEntity nonAttachedEntity = new AnyEntity();
    nonAttachedEntity.setId(newEntity.getId());

    // Save 2 to the database at next flush instead of 1!!!
    nonAttachedEntity.setValue(2);
    attachedEntity = em.merge(nonAttachedEntity);

    // This condition returns true
    // merge has found the already attached object (newEntity) and returns it.
    if(attachedEntity==newEntity) {
            System.out.print("They are the same object!");
    }

    // Set 3 to value
    attachedEntity.setValue(3);
    // Really, now both are the same object. Prints 3
    System.out.println(newEntity.getValue());

    // Modify the un attached object has no effect to the entity manager
    // nor to the other objects
    nonAttachedEntity.setValue(42);
}

この方法では、エンティティマネージャ内の任意のレジスタに対して1つの添付オブジェクトしか存在しません。

iDを持つエンティティのmerge()は次のようになります。

AnyEntity myMerge(AnyEntity entityToSave) {
    AnyEntity attached = em.find(AnyEntity.class, entityToSave.getId());
    if(attached==null) {
            attached = new AnyEntity();
            em.persist(attached);
    }
    BeanUtils.copyProperties(attached, entityToSave);

    return attached;
}

MySQLに接続した場合、merge()はON DUPLICATE KEY UPDATEオプションを指定したINSERTの呼び出しを使用してpersist()と同じくらい効率的になる可能性がありますが、JPAは非常に高水準のプログラミングであり、これが当てはまるとは限りません。

164
Josep Panadero

代入ジェネレータを使用している場合、 persistの代わりにmergeを使用すると、冗長なSQL文 が発生する可能性があるため、パフォーマンスに影響します。

また、 管理対象エンティティのマージの呼び出し も管理対象エンティティがHibernateによって自動的に管理され、それらの状態が ダーティチェックメカニズムによってデータベースレコードと同期されるため、誤りです。 upon フラッシュ持続コンテキスト

これらすべてがどのように機能するのかを理解するには、まずHibernateが開発者の考え方をSQL文から エンティティ状態遷移 にシフトすることを知っておく必要があります。

エンティティがHibernateによってアクティブに管理されると、すべての変更は自動的にデータベースに伝播されます。

Hibernateは現在接続されているエンティティを監視します。しかし、エンティティが管理されるためには、正しいエンティティ状態になっている必要があります。

まず、すべてのエンティティの状態を定義する必要があります。

  • 新規(トランジェント)

    HibernateのSession(a.k.aPersistence Context)に関連付けられておらず、データベーステーブルの行にマッピングされていない、新しく作成されたオブジェクトは、New(Transient)状態にあると見なされます。

    永続化するためには、EntityManager#persistメソッドを明示的に呼び出すか、または推移的な永続化メカニズムを利用する必要があります。

  • 永続的(管理型)

    永続エンティティはデータベーステーブルの行に関連付けられており、現在実行中の永続コンテキストによって管理されています。そのようなエンティティに対して行われた変更はすべて検出され、データベースに伝播されます(セッションフラッシュ時)。 Hibernateでは、INSERT/UPDATE/DELETEステートメントを実行する必要がなくなりました。 Hibernateは トランザクショナル後書き 作業スタイルを採用しており、変更は現在のSessionフラッシュ時間中の最後の責任がある瞬間に同期されます。

  • 切り離された

    現在実行中の永続コンテキストが閉じられると、以前に管理されていたエンティティはすべて切り離されます。連続した変更は追跡されなくなり、データベースの自動同期は行われなくなります。

    デタッチされたエンティティをアクティブなHibernateセッションに関連付けるには、次のいずれかのオプションを選択できます。

    • 再取り付け

      Hibernate(JPA 2.1ではなく)は、Session#updateメソッドによる再接続をサポートしています。 Hibernateセッションは、特定のデータベース行に対して1つのEntityオブジェクトしか関連付けることができません。これは、パーシスタンスコンテキストがメモリ内キャッシュ(第1レベルのキャッシュ)として機能し、特定のキー(エンティティタイプとデータベース識別子)に関連付けられるのは1つの値(エンティティ)だけだからです。現在のHibernateセッションに関連付けられているJVMオブジェクト(同じデータベース行に一致するもの)が他にない場合にのみ、エンティティを再接続できます。

    • マージ

    マージでは、デタッチされたエンティティの状態(ソース)を管理対象エンティティのインスタンス(宛先)にコピーします。マージエンティティが現在のセッションに同等のものがない場合は、データベースから取得されます。デタッチされたオブジェクトインスタンスは、マージ操作後もデタッチされたままになります。

  • 削除しました

    JPAは管理対象エンティティのみを削除することを許可することを要求していますが、Hibernateはデタッチされたエンティティを削除することもできます(ただし、Session#deleteメソッド呼び出しを通じてのみ)。削除されたエンティティは削除の予定があるだけで、実際のデータベースのDELETEステートメントはセッションフラッシュ時に実行されます。

JPAの状態遷移をよりよく理解するために、次の図を視覚化することができます。

enter image description here

Hibernate固有のAPIを使用している場合

enter image description here

134
Vlad Mihalcea

em.mergeを使用すると、JPAが生成しているフィールドがない場合でも、すべてのSELECTに対してINSERTステートメントが表示されることに気付きました。主キーフィールドは、自分で設定したUUIDでした。私はem.persist(myEntityObject)に切り替えて、INSERTステートメントだけを得ました。

37
Sarah Vessels

JPA仕様ではpersist()について次のように述べています。

Xがデタッチされたオブジェクトの場合は、永続化操作が呼び出されたときにEntityExistsExceptionがスローされるか、またはフラッシュ時またはコミット時にEntityExistsExceptionまたは別のPersistenceExceptionがスローされます。

そのため、オブジェクトがデタッチされたオブジェクトではない必要がある場合は、persist()を使用するのが適切です。あなたはそれが速く失敗するようにコードにPersistenceExceptionを投げさせることを好むかもしれません。

仕様は不明 ですが、persist()はオブジェクトの@GeneratedValue@Idを設定するかもしれません。しかしmerge()は既に生成された@Idを持つオブジェクトを持っていなければなりません。

28
Raedwald

mergepersistの間には、さらにいくつかの違いがあります(ここで、既にここに掲載されているものを再度列挙します)。

D1。 mergeは渡されたエンティティを管理対象にしませんが、管理対象の別のインスタンスを返します。反対側のpersistは、渡されたエンティティを管理対象にします。

//MERGE: passedEntity remains unmanaged, but newEntity will be managed
Entity newEntity = em.merge(passedEntity);

//PERSIST: passedEntity will be managed after this
em.persist(passedEntity);

D2。エンティティを削除してからそのエンティティを永続化することにした場合は、persist()を使用した場合のみ実行できます。mergeIllegalArgumentExceptionをスローするためです。

D3。手動でIDを管理することにした場合(たとえばUUIDを使用する場合)、merge操作はそのIDを持つ既存のエンティティを探すために後続のSELECTクエリをトリガしますが、persistはそれらのクエリを必要としない場合があります。

D4。コードを呼び出すコードを単純に信頼しない場合があります。データが更新されずに挿入されるようにするには、persistを使用する必要があります。

17
Andrei I

あなたが永続的なマージを使用するのに役立ちますマージについてのいくつかのより多くの詳細は永続化します:

元のエンティティ以外の管理対象インスタンスを返すことは、マージプロセスの重要な部分です。同じ識別子を持つエンティティインスタンスが永続コンテキストにすでに存在する場合、プロバイダはその状態をマージされているエンティティの状態で上書きしますが、すでに存在する管理対象バージョンをクライアントに返す必要があります。中古。プロバイダが永続コンテキストでEmployeeインスタンスを更新しなかった場合、そのインスタンスへの参照はマージされる新しい状態と矛盾します。

Merge()が新しいエンティティに対して呼び出されると、persist()操作と同じように動作します。エンティティを永続コンテキストに追加しますが、元のエンティティインスタンスを追加する代わりに、新しいコピーを作成してそのインスタンスを管理します。 merge()操作によって作成されたコピーは、persist()メソッドが呼び出された場合と同様に永続化されます。

関係が存在する場合、merge()操作は、切り離されたエンティティによって参照されるエンティティの管理バージョンを指すように管理エンティティを更新しようとします。エンティティが永続IDを持たないオブジェクトとの関係を持つ場合、マージ操作の結果は未定義です。管理コピーが非永続オブジェクトを指すことを許可するプロバイダもあれば、ただちに例外をスローするプロバイダもあります。このような場合は、例外が発生しないようにmerge()操作をオプションでカスケードすることができます。このセクションの後半でmerge()操作のカスケードについて説明します。マージされているエンティティが削除されたエンティティを指している場合は、IllegalArgumentException例外がスローされます。

遅延読み込み関係は、マージ操作では特別な場合です。遅延読み込み関係がデタッチされる前にエンティティでトリガーされていない場合、その関係はエンティティがマージされたときに無視されます。管理されている間に関係がトリガされ、エンティティがデタッチされている間にnullに設定された場合、エンティティの管理されたバージョンでもマージ中に関係がクリアされます。 "

上記の情報はすべて、Mike KeithとMerrick Schnicariolによる「Pro JPA 2 Mastering the Java™Persistence API」からの抜粋です。第6章セクションの切り離しとマージこの本は、実際には著者によってJPAに専念している2冊目の本です。この新しい本は、以前のものよりも多くの新しい情報を持っています。私はJPAに真剣に関わることになる人のためにこの本を読むことを本当にお勧めします。私の最初の答えを匿名で投稿して申し訳ありません。

16

セッション中の遅延ロードされたコレクションにアクセスしようとしていたため、エンティティでlazyLoading例外が発生しました。

私がすることは別のリクエストであり、セッションからエンティティを取得してから問題のある私のJSPページのコレクションにアクセスしようとしました。

これを軽減するために、コントローラ内の同じエンティティを更新してjspに渡しました。ただし、セッションで再保存したときに、SessionScopeでもアクセスできるようになり、LazyLoadingExceptionをスローしないようにしました。

以下は私のために働いています:

// scenario 2 MY WAY
// tran starts
e = new MyEntity();
e = em.merge(e); // re-assign to the same entity "e"

//access e from jsp and it will work dandy!!
8
logixplayer

答えを見ると、「カスケード」とIDの生成に関するいくつかの詳細が欠落しています。 質問を参照

また、使用する方法に応じて処理されるCascade.MERGECascade.PERSISTをマージして永続化するための別個のCascadeアノテーションを使用できることにも言及する価値があります。

仕様はあなたの友人です;)

6

Hibernateのドキュメントには、ユースケースが含まれているため、この説明が啓発的なものであることがわかりました。

Merge()の使用法と意味は、新しいユーザにとっては混乱しやすいようです。まず、あるエンティティマネージャにロードされたオブジェクト状態を別の新しいエンティティマネージャで使用しようとしていない限り、でmerge()を使用する必要はまったくありません。アプリケーション全体の中には、このメソッドを決して使用しないものがあります。

通常、merge()は以下のシナリオで使用されます。

  • アプリケーションが最初のエンティティマネージャにオブジェクトをロードします。
  • オブジェクトはプレゼンテーション層に渡されます
  • オブジェクトにいくつかの変更が加えられます
  • オブジェクトはビジネスロジック層に渡されます。
  • アプリケーションは、2番目のエンティティマネージャでmerge()を呼び出すことによってこれらの変更を保持します。

これはmerge()の正確な意味です。

  • 現在永続コンテキストに関連付けられている同じ識別子を持つ管理対象インスタンスがある場合は、指定されたオブジェクトの状態を管理対象インスタンスにコピーします。
  • 現在永続コンテキストに関連付けられている管理対象インスタンスがない場合は、データベースからそれをロードするか、新しい管理対象インスタンスを作成します。
  • 管理対象インスタンスが返される
  • 指定されたインスタンスは永続コンテキストと関連付けられず、切り離されたままで、通常は破棄されます。

投稿者: http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html

6
Ray Hulha

JPAは、Javaプラットフォーム上に構築されたエンタープライズアプリケーションの分野における紛れもないほどの単純化です。 J2EEの古いエンティティBeanの複雑さに対処しなければならなかった開発者として、私はJava EE仕様の中にJPAを含めることが大きな飛躍として見られています。しかし、JPAの詳細を深く掘り下げながら、私はそれほど簡単ではないことを見つけました。この記事では、EntityManagerのmergeメソッドとpersistメソッドの比較について説明します。これらのメソッドが重なると、初心者だけでなく混乱が生じる可能性があります。さらに、両方の方法をより一般的な方法の特別な場合の組み合わせとして見なす一般化を提案します。

永続エンティティ

Mergeメソッドとは対照的に、persistメソッドは非常に簡単で直感的です。永続メソッドの使用法の最も一般的なシナリオは、次のようにまとめることができます。

エンティティクラスの新しく作成されたインスタンスはpersistメソッドに渡されます。このメソッドが返されると、エンティティはデータベースへの挿入のために管理および計画されます。トランザクションのコミット時またはフラッシュメソッドの呼び出し時に発生します。エンティティがPERSISTカスケード戦略でマークされた関係を通して他のエンティティを参照する場合、この手順はそれにも適用されます。」

enter image description here

仕様はより詳細に入ります、しかし、これらの詳細が多かれ少なかれエキゾチックな状況だけをカバーするので、それらを覚えていることは重要ではありません。

エンティティの結合

永続化と比較して、マージの動作の説明はそれほど単純ではありません。それが持続する場合のように、主なシナリオはありません、そしてプログラマーは正しいコードを書くためにすべてのシナリオを覚えていなければなりません。 JPAの設計者は、(主に新しく作成されたエンティティを処理するpersistメソッドとは反対に)独立したエンティティを処理することを主な関心事とする何らかの方法を望んでいたようです。永続コンテキスト内で管理対象の相手に管理されていないエンティティ(引数として渡される)。ただし、この作業ではさらにいくつかのシナリオに分割され、メソッド全体の動作の理解度が低下します。

JPA仕様の段落を繰り返す代わりに、マージ方法の動作を概略的に示すフロー図を用意しました。

enter image description here

それでは、いつpersistを使ってマージするべきですか?

永続化

  • メソッドが常に新しいエンティティを作成し、エンティティを更新しないようにする必要があります。そうでない場合、このメソッドは主キーの一意性違反の結果として例外をスローします。
  • バッチ処理、ステートフルな方法でエンティティを処理します(ゲートウェイパターンを参照)。
  • パフォーマンスの最適化

マージ

  • このメソッドでデータベースにエンティティを挿入または更新する必要があります。
  • エンティティをステートレスな方法で処理したい(サービス内のデータ転送オブジェクト)
  • まだ作成されている可能性があるがまだ作成されていない可能性がある別のエンティティへの参照を持つ可能性がある新しいエンティティを挿入します。たとえば、新しいアルバムまたは既存のアルバムへの参照を含む新しい写真を挿入します。
6
Amit Gujarathi

シナリオX:

テーブル:Spitter(One)、テーブル:Spittles(Many)(SpittlesはFKとの関係のオーナー:spitter_id)

このシナリオでは、保存が行われます。同じSpitterが所有しているかのように、Spitterと両方のSpittles。

        Spitter spitter=new Spitter();  
    Spittle spittle3=new Spittle();     
    spitter.setUsername("George");
    spitter.setPassword("test1234");
    spittle3.setSpittle("I love Java 2");       
    spittle3.setSpitter(spitter);               
    dao.addSpittle(spittle3); // <--persist     
    Spittle spittle=new Spittle();
    spittle.setSpittle("I love Java");
    spittle.setSpitter(spitter);        
    dao.saveSpittle(spittle); //<-- merge!!

シナリオY:

これはSpitterを保存し、2 Spittlesを保存しますが、それらは同じSpitterを参照しません!

        Spitter spitter=new Spitter();  
    Spittle spittle3=new Spittle();     
    spitter.setUsername("George");
    spitter.setPassword("test1234");
    spittle3.setSpittle("I love Java 2");       
    spittle3.setSpitter(spitter);               
    dao.save(spittle3); // <--merge!!       
    Spittle spittle=new Spittle();
    spittle.setSpittle("I love Java");
    spittle.setSpitter(spitter);        
    dao.saveSpittle(spittle); //<-- merge!!

いつpersistを使用するかmergeを使用するかについてのアドバイスがあるかもしれません。状況によって異なると思います。新しいレコードを作成する必要がある可能性はどのくらいありますか。また、永続データを取得するのはどれほど難しいのでしょう。

自然なキー/識別子を使用できるとしましょう。

  • データは永続化する必要がありますが、レコードが存在し、更新が求められることがあります。この場合、永続化を試すことができ、それがEntityExistsExceptionを投げたら、それを調べてデータを結合します。

    {entityManager.persist(entity)}をお試しください

    catch(EntityExistsException exception){/ *検索とマージ* /}

  • 永続データは更新する必要がありますが、時々データの記録がありません。この場合、あなたはそれを調べて、実体が欠けているならば固執する:

    entity = entityManager.find(key);

    if(entity == null){entityManager.persist(entity); }

    そうでなければ{/ * merge * /}

あなたが自然なキー/識別子を持っていないならば、あなたは実体が存在するかどうか、またはどのようにそれを調べるべきかを理解するのにより困難な時間を持つでしょう。

マージは2つの方法で対処することができます。

  1. 変更が通常小さい場合は、それらを管理対象エンティティに適用します。
  2. 変更が一般的な場合は、変更されていないデータと共に、永続化されたエンティティからIDをコピーします。それからEntityManager :: merge()を呼び出して古いコンテンツを置き換えます。
1
Peter Willems

もう一つの観察:

merge()は自動生成されたid(IDENTITYSEQUENCEでテストされています)についてのみ、そのようなidを持つレコードがあなたのテーブルに存在する場合にのみ気にします。その場合、merge()はレコードを更新しようとします。ただし、idが存在しないか既存のレコードと一致しない場合、merge()はそれを完全に無視し、新しいレコードを割り当てるようにdbに要求します。これは時々多くのバグの原因となります。新しいレコードのIDを強制するためにmerge()を使用しないでください。

一方、persist()は決してidを渡さないようにします。すぐに失敗します。私の場合は、

原因:org.hibernate.PersistentObjectException:永続化オブジェクトに渡された分離エンティティ

hibernate-jpa javadocにはヒントがあります。

:javax.persistence.EntityExistsException - エンティティがすでに存在する場合(エンティティがすでに存在する場合は、永続化操作が呼び出されたときにEntityExistsExceptionがスローされるか、フラッシュ時またはコミット時にEntityExistsExceptionまたは別のPersistenceExceptionがスローされる可能性があります。)

1
yuranos87

persist(entity)は全く新しいエンティティと共に使用してそれらをDBに追加するべきです(entityが既にDBに存在する場合はEntityExistsExceptionがスローされます).

エンティティがデタッチされて変更された場合にエンティティを永続コンテキストに戻すには、merge(entity)を使用する必要があります。

おそらく持続するINSERT SQLステートメントとマージUPDATE SQLステートメントを生成しています(しかし、私はよくわかりません)。

0
Krystian