EntityManager.merge()
新しいオブジェクトを挿入し、既存のものを更新できます。
persist()
なぜ(新しいオブジェクトしか作成できない)を使いたいのでしょうか?
EntityManager.merge()
新しいオブジェクトを挿入し、既存のものを更新できます。
persist()
なぜ(新しいオブジェクトしか作成できない)を使いたいのでしょうか?
どちらの方法でも、エンティティを PersistenceContext に追加します。違いは、後でエンティティをどうするかです。
Persist はエンティティ インスタンスを取得し、それをコンテキストに追加して、そのインスタンスを管理対象にします (つまり、エンティティに対する将来の更新が追跡されます)。
Merge は、状態がマージされたマネージド インスタンスを返します。PersistenceContext に存在するものを返すか、エンティティの新しいインスタンスを作成します。いずれの場合も、提供されたエンティティから状態をコピーし、マネージド コピーを返します。渡されたインスタンスは管理されません (マージを再度呼び出さない限り、行った変更はトランザクションの一部ではありません)。ただし、返されたインスタンス (マネージド インスタンス) を使用できます。
コード例が役立つかもしれません。
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 を使用したい場合もあります。
Persist と Merge は 2 つの異なる目的のためのものです (それらはまったく代替手段ではありません)。
(差分情報を展開するために編集)
持続:
マージ:
persist() 効率:
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 は非常に高レベルのプログラミングであり、これがどこでも当てはまるとは想定できません。
割り当てられたジェネレーターを使用している場合、merge
代わりに使用persist
すると冗長な SQL ステートメントが発生し、パフォーマンスに影響を与える可能性があります。
merge
また、マネージド エンティティは Hibernate によって自動的に管理され、Persistence Context のフラッシュ時にダーティ チェック メカニズムによってその状態がデータベース レコードと同期されるため、マネージド エンティティの呼び出しも間違いです。
これらすべてがどのように機能するかを理解するには、まず、Hibernate が開発者の考え方を SQL ステートメントからエンティティの状態遷移にシフトすることを知っておく必要があります。
エンティティが Hibernate によってアクティブに管理されると、すべての変更が自動的にデータベースに反映されます。
Hibernate は、現在アタッチされているエンティティを監視します。ただし、エンティティが管理対象になるには、適切なエンティティ状態である必要があります。
JPA の状態遷移をよりよく理解するために、次の図を視覚化できます。
または、Hibernate 固有の API を使用する場合:
上の図に示すように、エンティティは次の 4 つの状態のいずれかになります。
Session
Hibernate (aka )に関連付けられたことがなくPersistence Context
、どのデータベース テーブル行にもマップされていない新しく作成されたオブジェクトは、New (Transient) 状態にあると見なされます。
永続化するには、メソッドを明示的に呼び出すかEntityManager#persist
、推移的な永続化メカニズムを利用する必要があります。
永続的 (マネージド)
永続エンティティはデータベース テーブルの行に関連付けられており、現在実行中の永続コンテキストによって管理されています。このようなエンティティに加えられた変更はすべて検出され、データベースに伝播されます (セッションのフラッシュ時間中)。Hibernate を使用すると、INSERT/UPDATE/DELETE ステートメントを実行する必要がなくなりました。Session
Hibernate はトランザクショナルな後書き作業スタイルを採用しており、変更は現在のフラッシュ時間中の最後の責任ある瞬間に同期されます。
切り離された
現在実行中の永続コンテキストが閉じられると、以前に管理されていたエンティティはすべて切り離されます。連続する変更は追跡されなくなり、データベースの自動同期は行われません。
切り離されたエンティティをアクティブな Hibernate セッションに関連付けるには、次のいずれかのオプションを選択できます。
再取り付け
Hibernate (JPA 2.1 ではない) は、Session#update メソッドによる再接続をサポートしています。
Hibernate セッションは、特定のデータベース行に対して 1 つのエンティティ オブジェクトのみを関連付けることができます。これは、永続コンテキストがメモリ内キャッシュ (第 1 レベルのキャッシュ) として機能し、1 つの値 (エンティティ) のみが特定のキー (エンティティ タイプとデータベース識別子) に関連付けられているためです。
現在の Hibernate セッションにすでに関連付けられている (同じデータベース行に一致する) 他の JVM オブジェクトがない場合にのみ、エンティティを再接続できます。
マージ
マージは、切り離されたエンティティの状態 (ソース) を管理対象のエンティティ インスタンス (宛先) にコピーします。マージするエンティティが現在のセッションに同等のものを持たない場合は、データベースから取得されます。
切り離されたオブジェクト インスタンスは、マージ操作後も引き続き切り離されたままになります。
削除する
JPA は管理されたエンティティのみを削除できることを要求していますが、Hibernate は切り離されたエンティティも削除できます (ただし、Session#delete メソッド呼び出しのみ)。
削除されたエンティティは削除のみがスケジュールされ、実際のデータベースの DELETE ステートメントはセッションのフラッシュ時に実行されます。
em.merge
を使用すると、JPAが生成しているフィールドがない場合でも、すべてのSELECT
ステートメントが表示されることに気付きINSERT
ました。主キーフィールドは自分で設定したUUIDでした。私は切り替えて、ステートメントem.persist(myEntityObject)
だけを受け取りました。INSERT
JPA仕様では、について次のように述べていますpersist()
。
Xが分離オブジェクトの場合
EntityExistsException
、persist操作が呼び出されたときにがスローされるか、フラッシュまたはコミット時にEntityExistsException
または別のオブジェクトがスローされる可能性があります。PersistenceException
したがって、オブジェクトが分離オブジェクトであってはならないpersist()
場合は、を使用するのが適切です。すぐに失敗するように、コードをスローすることをお勧めします。PersistenceException
仕様は不明ですが、オブジェクトにをpersist()
設定する場合があります。ただし、すでに生成されたオブジェクトが必要です。@GeneratedValue
@Id
merge()
@Id
と の間にはさらにいくつかの違いがmerge
ありますpersist
(ここに既に投稿されているものを再度列挙します)。
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() を使用してのみ行うことができmerge
ますIllegalArgumentException
。
D3. ID を手動で (UUID を使用して)処理することにした場合、その ID を持つ既存のエンティティを探すために、merge
操作によって後続のクエリがトリガーされますが、それらのクエリは必要ない場合があります。SELECT
persist
D4. コードを呼び出すコードを単純に信頼しない場合があります。データが更新されず、挿入されることを確認するには、 を使用する必要がありますpersist
。
永続化よりもマージを使用するのに役立つマージに関する詳細:
元のエンティティ以外のマネージド インスタンスを返すことは、マージ プロセスの重要な部分です。同じ識別子を持つエンティティ インスタンスが永続化コンテキストに既に存在する場合、プロバイダーはマージされるエンティティの状態でその状態を上書きしますが、既に存在するマネージド バージョンをクライアントに返す必要があります。使用済み。プロバイダーが永続コンテキストで 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 に真剣に取り組む人には、この本を読むことを強くお勧めします。最初の回答を匿名で投稿して申し訳ありません。
JPA は、Java プラットフォーム上に構築されたエンタープライズ アプリケーションの領域を大幅に簡素化したものであることは間違いありません。J2EE の古いエンティティー Bean の複雑さに対処しなければならなかった開発者として、Java EE 仕様に JPA が組み込まれたことは、大きな飛躍であると考えています。しかし、JPA の詳細を掘り下げると、それほど簡単ではないことがわかります。この記事では、EntityManager のマージ メソッドと永続化メソッドの比較を扱います。これらの重複する動作は、初心者だけでなく混乱を招く可能性があります。さらに、両方の方法を、より一般的な方法を組み合わせた特殊なケースと見なす一般化を提案します。
永続エンティティ
merge メソッドとは対照的に、persist メソッドは非常に単純で直感的です。persist メソッドの使用の最も一般的なシナリオは、次のように要約できます。
「エンティティ クラスの新しく作成されたインスタンスが永続メソッドに渡されます。このメソッドが返された後、エンティティは管理され、データベースへの挿入が計画されます。これは、トランザクションのコミット時またはその前、またはフラッシュ メソッドが呼び出されたときに発生する可能性があります。エンティティが PERSIST カスケード戦略でマークされた関係を介して別のエンティティを参照する場合、この手順はエンティティにも適用されます。」
仕様はより詳細になりますが、これらの詳細は多かれ少なかれエキゾチックな状況のみをカバーするため、それらを覚えておくことは重要ではありません.
エンティティのマージ
永続化と比較して、マージの動作の説明はそれほど単純ではありません。永続化の場合のように主要なシナリオはなく、プログラマーは正しいコードを記述するためにすべてのシナリオを覚えておく必要があります。JPAの設計者は、分離されたエンティティを処理することを主な関心事とするメソッドが必要だったようです(新しく作成されたエンティティを主に処理するpersistメソッドとは対照的に)。管理されていないエンティティ (引数として渡される) を、永続化コンテキスト内の対応する管理対象に変換します。ただし、このタスクはさらにいくつかのシナリオに分割され、メソッド全体の動作の理解度が低下します。
JPA 仕様の段落を繰り返す代わりに、merge メソッドの動作を概略的に示すフロー図を用意しました。
では、いつ永続化を使用し、いつマージする必要がありますか?
持続する
マージ
セッション中の遅延ロードされたコレクションにアクセスしようとしたため、エンティティで lazyLoading 例外が発生していました。
私がすることは、別のリクエストで、セッションからエンティティを取得してから、問題のある jsp ページのコレクションにアクセスしようとすることでした。
これを軽減するために、コントローラーで同じエンティティを更新してjspに渡しましたが、セッションで再保存すると、アクセス可能になり、例外SessionScope
をスローしないと思いますがLazyLoadingException
、例2の変更:
以下は私のために働いています:
// 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!!
シナリオ X:
Table:Spitter (One) ,Table: Spittles (Many) (Spittles は FK:spitter_id との関係の所有者です)
このシナリオの結果、次のように保存されます。同じスピッターが所有しているかのように、スピッターと両方のスピトル。
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(entity) は、完全に新しいエンティティで使用して、それらを DB に追加する必要があります (エンティティが DB に既に存在する場合は、EntityExistsException がスローされます)。
エンティティが切り離されて変更された場合、エンティティを永続化コンテキストに戻すには、merge(entity) を使用する必要があります。
おそらく永続化は INSERT sql ステートメントとマージ UPDATE sql ステートメントを生成しています (しかし、よくわかりません)。