EntityManager.merge()
新しいオブジェクトを挿入し、既存のオブジェクトを更新できます。
なぜ使用したいのですかpersist()
(新しいオブジェクトしか作成できません)?
EntityManager.merge()
新しいオブジェクトを挿入し、既存のオブジェクトを更新できます。
なぜ使用したいのですかpersist()
(新しいオブジェクトしか作成できません)?
回答:
どちらの方法でも、エンティティをPersistenceContextに追加します。違いは、エンティティを後でどのように処理するかです。
Persistはエンティティインスタンスを取得してコンテキストに追加し、そのインスタンスを管理します(つまり、エンティティへの将来の更新が追跡されます)。
Mergeは、状態がマージされたマネージインスタンスを返します。PersistenceContextに存在するものを返すか、エンティティの新しいインスタンスを作成します。いずれの場合も、提供されたエンティティから状態をコピーし、マネージドコピーを返します。渡したインスタンスは管理されません(変更を加えても、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を使用したい場合があります。
merge
、オブジェクトを管理する前の完全なコピーがパフォーマンスに影響を与えるでしょうか?
@GeneratedId
、私はシナリオ2でそれを得ることができますか?
永続化とマージは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は非常に高水準のプログラミングであり、これがどこでも当てはまるとは限りません。
em.persist(x)
ではx = em.merge(x)
?
merge()
スローすることもできますEntityExistsException
RuntimeException
、Javadocには記載されていません。
割り当てられたジェネレーターを使用している場合、persistの代わりにmergeを使用すると、冗長なSQLステートメントが発生し、パフォーマンスに影響を与える可能性があります。
また、マネージエンティティはHibernateによって自動的に管理され、その状態はPersistence Contextのフラッシュ時にダーティチェックメカニズムによってデータベースレコードと同期されるため、マネージエンティティのマージの呼び出しも誤りです。
これらすべてがどのように機能するかを理解するには、まず、Hibernateが開発者の考え方をSQLステートメントからエンティティの状態遷移にシフトすることを知っておく必要があります。
エンティティがHibernateによってアクティブに管理されると、すべての変更が自動的にデータベースに伝達されます。
Hibernateは現在接続されているエンティティを監視します。ただし、エンティティが管理対象になるには、適切なエンティティ状態である必要があります。
JPAの状態遷移をよりよく理解するために、次の図を視覚化できます。
または、Hibernate固有のAPIを使用する場合:
上の図に示すように、エンティティは次の4つの状態のいずれかになります。
新規(一時的)
Hibernate Session
(aka Persistence Context
)に関連付けられておらず、データベーステーブル行にマップされていない、新しく作成されたオブジェクトは、新規(一時的)状態と見なされます。
永続化するには、EntityManager#persist
メソッドを明示的に呼び出すか、推移的永続化メカニズムを利用する必要があります。
永続的(管理)
永続エンティティはデータベーステーブルの行に関連付けられており、現在実行中の永続コンテキストによって管理されています。このようなエンティティに加えられた変更はすべて検出され、データベースに伝達されます(セッションのフラッシュ時)。Hibernateを使用すると、INSERT / UPDATE / DELETEステートメントを実行する必要がなくなります。Hibernateはトランザクション後書き作業スタイルを採用しており、変更は現在のSession
フラッシュ時間中の最後の責任ある瞬間に同期されます。
戸建
現在実行中の永続コンテキストが閉じられると、以前に管理されていたすべてのエンティティが切り離されます。継続的な変更は追跡されなくなり、自動データベース同期は行われません。
デタッチされたエンティティをアクティブなHibernateセッションに関連付けるには、次のオプションのいずれかを選択できます。
再接続
Hibernate(JPA 2.1ではない)は、Session#updateメソッドを介した再接続をサポートしています。Hibernateセッションは、特定のデータベース行に対して1つのEntityオブジェクトのみを関連付けることができます。これは、永続コンテキストがメモリ内キャッシュ(1次レベルキャッシュ)として機能し、1つの値(エンティティ)のみが特定のキー(エンティティタイプとデータベース識別子)に関連付けられているためです。エンティティを再接続できるのは、現在のHibernateセッションにすでに関連付けられている(同じデータベース行に一致する)他のJVMオブジェクトがない場合のみです。
マージ
マージにより、分離されたエンティティの状態(ソース)が管理対象エンティティのインスタンス(宛先)にコピーされます。現在のセッションにマージするエンティティに同等のものがない場合、データベースから1つがフェッチされます。デタッチされたオブジェクトインスタンスは、マージ操作後もデタッチされたままになります。
削除されました
JPAは管理対象エンティティのみを削除することを要求していますが、Hibernateは分離されたエンティティを削除することもできます(ただし、Session#deleteメソッド呼び出しによってのみ)。削除されたエンティティは削除のみがスケジュールされ、実際のデータベースのDELETEステートメントはセッションのフラッシュ時に実行されます。
JPAが生成するフィールドがない場合でもem.merge
、を使用すると、SELECT
ごとINSERT
にステートメントが表示されることに気付きました。主キーフィールドは自分で設定したUUIDでした。私はそれに切り替えてem.persist(myEntityObject)
、ちょうどINSERT
ステートメントを得ました。
merge()
。複雑なビューを持つPostgreSQLデータベースがありました。ビューはいくつかのテーブルからのデータを集計しました(テーブルの構造は同じですが、名前が異なります)。したがって、JPAはを試行しましたがmerge()
、実際にはJPAが最初に作成されSELECT
(ビュー設定が原因でデータベースが異なるテーブルから同じ主キーを持つ複数のレコードを返す可能性がありました!)、次にJPA(Hibernateが実装)が失敗しました:同じキーを持つ複数のレコードがあります(org.hibernate.HibernateException: More than one row with the given identifier was found
)。私の場合persist()
、私を助けました。
JPA仕様では、について次のように述べられていますpersist()
。
場合Xは分離オブジェクトで、
EntityExistsException
操作が呼び出された、または持続ときスローされるEntityExistsException
、または別のPersistenceException
フラッシュでスロー又は時間をコミットすることができます。
そのpersist()
ため、オブジェクトが分離されたオブジェクトであってはならない場合に使用するのが適切です。あなたはコードにスローさせることを好むかもしれませんPersistenceException
ようにして、速く失敗ようにすることもできます。
が、仕様が不明である、persist()
設定するかもしれません@GeneratedValue
@Id
オブジェクトのために。merge()
ただし、@Id
生成済みのオブジェクトが必要です。
merge()
が持つオブジェクト持っている必要があり@Id
、既に生成されたが。」。EntityManagerがオブジェクトIDのフィールドの値を見つけられない場合は常に、DBに永続化(挿入)されます。
永続的なマージを使用するのに役立つマージに関する詳細:
元のエンティティ以外のマネージドインスタンスを返すことは、マージプロセスの重要な部分です。同じ識別子を持つエンティティインスタンスが永続化コンテキストに既に存在する場合、プロバイダーはその状態をマージされるエンティティの状態で上書きしますが、既存の管理対象バージョンをクライアントに返して、中古。プロバイダーが永続コンテキストで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に真剣に関与する人のために読むことを本当にお勧めします。最初の回答を匿名で投稿して申し訳ありません。
との間にはさらにいくつかの違いが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。エンティティを削除してから、そのエンティティを永続化することにした場合は、merge
がスローされるため、persist()を使用してのみそれを実行できますIllegalArgumentException
。
D3。IDを手動で管理することを決定した場合(UUIDを使用するなど)、そのIDを持つ既存のエンティティーを探すために、merge
後続のSELECT
クエリがトリガーされますが、persist
それらのクエリは必要ない場合があります。
D4。コードを呼び出すコードを単に信頼しない場合があり、データが更新されずに挿入されることを確認するには、を使用する必要がありますpersist
。
セッション中の遅延ロードされたコレクションにアクセスしようとしたため、エンティティで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!!
使用例が含まれているため、Hibernateドキュメントからこの説明がわかりました。
merge()の使用法とセマンティクスは、新規ユーザーを混乱させるようです。まず、あるエンティティマネージャーに読み込まれたオブジェクトの状態を別の新しいエンティティマネージャーで使用しない限り、merge()をまったく使用する必要はありません。一部のアプリケーション全体では、この方法を使用しません。
通常、merge()は次のシナリオで使用されます。
- アプリケーションは最初のエンティティーマネージャーにオブジェクトをロードします
- オブジェクトはプレゼンテーション層に渡されます
- オブジェクトにいくつかの変更が加えられています
- オブジェクトはビジネスロジックレイヤーに渡されます。
- アプリケーションは、2番目のエンティティマネージャーでmerge()を呼び出すことにより、これらの変更を永続化します
次に、merge()の正確なセマンティクスを示します。
- 現在永続コンテキストに関連付けられている同じ識別子を持つマネージドインスタンスがある場合は、指定されたオブジェクトの状態をマネージドインスタンスにコピーします
- 現在永続コンテキストに関連付けられているマネージドインスタンスがない場合は、データベースからロードするか、新しいマネージドインスタンスを作成してください
- マネージドインスタンスが返されます
- 指定されたインスタンスは永続化コンテキストに関連付けられず、デタッチされたままであり、通常は破棄されます
送信元:http : //docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html
答えを調べてみると、 `Cascade 'とID生成に関していくつかの詳細が欠けています。 質問を見る
また、あなたが別の持つことができることを言及する価値があるCascade
マージと持続のための注釈を:Cascade.MERGE
とCascade.PERSIST
いるが、使用される方法に従って処理されます。
仕様はあなたの友達です;)
JPAは、間違いなく、Javaプラットフォーム上に構築されたエンタープライズアプリケーションのドメインにおける優れた単純化です。J2EEの古いエンティティBeanの複雑さに対処しなければならなかった開発者として、Java EE仕様にJPAを含めることは大きな前進であると私は思います。しかし、JPAの詳細をさらに掘り下げていくと、それほど簡単ではないことがわかります。この記事では、EntityManagerのmergeメソッドとpersistメソッドの比較について扱います。これらのメソッドの重複動作は、初心者だけでなく混乱を招く可能性があります。さらに、両方の方法を、より一般的な方法の特殊なケースを組み合わせたものと見なす一般化を提案します。
永続エンティティ
mergeメソッドとは対照的に、persistメソッドはかなり単純で直感的です。持続メソッドの使用の最も一般的なシナリオは、次のように要約できます。
「新しく作成されたエンティティークラスのインスタンスは、persistメソッドに渡されます。このメソッドが返された後、エンティティーは管理され、データベースへの挿入が計画されています。エンティティがPERSISTカスケード戦略でマークされた関係を介して別のエンティティを参照する場合、この手順もそれに適用されます。」
仕様はより詳細になりますが、これらの詳細は多かれ少なかれエキゾチックな状況のみを対象としているため、それらを覚えておくことは重要ではありません。
エンティティのマージ
永続化と比較すると、マージの動作の説明はそれほど単純ではありません。永続化の場合のように、主なシナリオはありません。プログラマは、正しいコードを書くためにすべてのシナリオを覚えておく必要があります。JPAデザイナーは、分離されたエンティティを処理することを主な関心事とするいくつかのメソッドを望んでいるようです(新しく作成されたエンティティを主に処理する永続メソッドとは反対に)。マージメソッドの主なタスクは、非永続エンティティ(引数として渡されます)を、永続コンテキスト内の対応する管理対象に渡します。ただし、このタスクはさらにいくつかのシナリオに分かれ、メソッド全体の動作の理解度を低下させます。
JPA仕様の段落を繰り返す代わりに、マージメソッドの動作を概略的に示すフロー図を用意しました。
では、永続化を使用する必要があるのはいつですか?
固執する
マージ
シナリオX:
Table:Spitter(One)、Table:Spittles(Many)(SpittlesはFK:spitter_idとの関係の所有者です)
このシナリオでは、保存が行われます。Spitterと両方のSpittlesが、Same 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.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!!
別の観察:
merge()
このようなIDのレコードがテーブルに既に存在する場合にのみ、自動生成されたID(IDENTITY
およびでテストSEQUENCE
)が考慮されます。その場合merge()
、レコードを更新しようとします。ただし、IDがないか、既存のレコードと一致しない場合は、ID merge()
を完全に無視して、新しいID を割り当てるようにデータベースに要求します。これは、多くのバグの原因になることがあります。merge()
新しいレコードのIDを強制するために使用しないでください。
persist()
一方、IDを渡すことさえできません。すぐに失敗します。私の場合、それは:
原因:org.hibernate.PersistentObjectException:永続化に渡された分離エンティティ
hibernate-jpa javadocにヒントがあります:
例外:javax.persistence.EntityExistsException-エンティティがすでに存在する場合。(エンティティがすでに存在する場合、永続化操作が呼び出されたときにEntityExistsExceptionがスローされるか、フラッシュ時またはコミット時にEntityExistsExceptionまたは別のPersistenceExceptionがスローされる場合があります。)
persist()
IDがあることについて文句を言うのではなく、同じIDを持つ何かがすでにデータベースにある場合に文句を言うだけです。
永続化をいつ使用し、いつマージを使用するかについてのアドバイスがここにあるかもしれません。それは状況に依存すると思います。新しいレコードを作成する必要がある可能性と、永続化されたデータを取得するのがどれほど難しいかです。
自然なキー/識別子を使用できると仮定しましょう。
データは永続化する必要がありますが、たまにレコードが存在し、更新が必要です。この場合、永続化を試すことができ、それがEntityExistsExceptionをスローする場合は、それを調べてデータを結合します。
{entityManager.persist(entity)}を試してください
catch(EntityExistsException exception){/ *取得してマージ* /}
永続化されたデータを更新する必要がありますが、データの記録がまだない場合があります。この場合、それを調べ、エンティティが見つからない場合は永続化します。
entity = entityManager.find(key);
if(entity == null){entityManager.persist(entity); }
それ以外の場合は{/ *マージ* /}
自然なキー/識別子がない場合、エンティティが存在するかどうか、またはどのように検索するかを判断するのが難しくなります。
マージも2つの方法で処理できます。
それらをDBに追加するには、persist(entity)をまったく新しいエンティティで使用する必要があります(エンティティがDBにすでに存在する場合は、EntityExistsExceptionがスローされます)。
merge(entity)を使用して、エンティティがデタッチされて変更された場合にエンティティを永続コンテキストに戻す必要があります。
おそらく永続化はINSERT SQLステートメントとマージUPDATE SQLステートメントを生成しています(しかし、私にはわかりません)。