JPA EntityManagerがマージ時にデータベース挿入を実行していますか?
-
03-07-2019 - |
質問
下でHibernateでJPAを使用していますが、マージを機能させるのに問題があります。 しかし、JPAで直面している問題を説明する前に、問題が私のアプローチに起因する場合に備えて、達成しようとしていることを説明しましょう。
別のシステムに配置する必要があるシステム上のデータがあります。これを行うには、データを読み取り、そのデータに基づいて新しいORMオブジェクトを構築します。次に、ORMオブジェクトをデータベースに永続化します。データベースが空の場合、プログラムは問題なく機能し、単純なem.persist(object)呼び出しで動作します。ただし、データベースにデータがあるときにこのプロセスを実行し、必要に応じて新しいデータを追加し、古いデータを更新できるようにしたいと考えています。ここで問題が発生しています。
項目がデータベースに既に存在するかどうかを確認するために簡単なチェックを行います。何も見つからない場合は保持し、レコードが存在する場合はマージを試みます。重複レコードエラーで失敗します。
ERROR JDBCExceptionReporter - Violation of UNIQUE KEY constraint 'UK-SubStuff-StuffId-SubStuffNumber'. Cannot insert duplicate key in object 'SubStuff'.
em.merge()呼び出しが更新ではなく挿入を試行しているのは奇妙に思えます(SQLロギングで確認しました)。
Hibernate: insert into SubStuff (SubStuffNumber, StuffId, Name, TypeId) values (?, ?, ?, ?)
サブオブジェクトにカスケードするオブジェクトを使用していることに注意してください。サブオブジェクトで障害が発生しています。最初にサブオブジェクトをマージしようとするので、私にとっては理にかなっています。
以下は私のコードです。おおまかな状態を許してください。ここでいくつかの回避策を文書化しようとしました。
private void storeData(Collection<Stuff> Stuffs) {
for (Stuff stuff : Stuffs) {
//I think this first block can be safely ignored, as I am having no issues with it
// Left it in just in case someone more experianced then I sees the root of the issue here.
Collection<SubStuff> subStuffs = stuff.getSubStuffCollection();
for (SubStuff s : subStuffs) {
//Persist SubStuff Type, which DOES NOT cascade,
// due to it not having an internal SubStuff collection
Query q = em.createNamedQuery("SubStuffType.findByType");
q.setParameter("type", f.getTypeId().getType());
try {
SubStuffType sst = (SubStuffType) q.getSingleResult();
s.setTypeId(sst);
} catch (NoResultException ex) {
if (logger.isDebugEnabled()) logger.debug("SubStuff Type not found, persisting");
em.persist(s.getTypeId());
}
}
if (em.find(Stuff.class, stuff.getId()) == null) {
//Persist on Stuffs will cascade to SubStuffs
em.persist(stuff);
} else {
// Failing to merge SubStuff, tries to insert duplicate
// Merge SubStuff first
// The block below is my attempt to merge the SubStuff Collection before merging Stuff,
// it creates the same isuse as a straight merge of Stuff.
Collection<SubStuff> mergedSubStuffs = new ArrayList<SubStuff>(SubStuffs.size());
for (SubStuff s : SubStuffs) {
Query q = em.createNamedQuery("SubStuff.findBySubStuffNumberStuffId");
q.setParameter("SubStuffNumber", s.getSubStuffNumber());
q.setParameter("StuffId", stuff.getId());
try {
SubStuff subStuff = (SubStuff) q.getSingleResult();
// -----> Merge fails, with an duplicate insert error
SubStuff mergedSubStuff = em.merge(s);
mergedSubStuffs.add(mergedSubStuff);
} catch (NoResultException ex) {
throw ex;
}
}
stuff.setSubStuffCollection(mergedSubStuffs);
// -----> This will fails with same error as above, if I remove the attempt
// to merge the sub objects
em.merge(stuff);
}
}
}
JPAの経験がある人が私を助けてくれたら、本当に感謝しています。 HibernateのsaveOrUpdate()とJPAのmerge()の違いは明らかに私をつまずかせますが、EnityMangerのマージに関するいくつかの記事を読んでも、ここで何が起こっているのかについて頭を包むことはできません。
お時間をいただきありがとうございます。
解決
StackOverflowの呪いが再び発生しました。この問題に1日ほど取り組んだ後、この質問を投稿することにしましたが、20分以内にユーレカの瞬間があり、それを解決しました。質問を投稿するのに十分な自分の考えを明確にしたからです。どの情報が質問に関連しているのかを考える際に、自動生成されたキーが原因であることに気付きました(または、マージ時に、これらのキーを正しく処理しませんでした)。
私の問題は、SubStuff(最悪の代替ネーミングスキーム、申し訳ありません)に自動生成された人工主キーがあることです。だから、マージするとき、私はする必要がありました;
SubStuff subStuff = (SubStuff) q.getSingleResult();
//++++++++
s.setId(subStuff.getId());
//++++++++
//The following code can be removed.
//--- SubStuff mergedSubStuff = em.merge(f);
//--- mergedSubStuffs.add(mergedSubStuff);
これにより、データベースにすでに存在する行に主キーが設定され、最初のテストでは正常に機能するようです。
マージの呼び出しは、サブスタッフオブジェクトをカスケードするem.merge(stuff)の呼び出しに単純化できます。ループ内でマージを実行しなくなったため、mergedsubstuffコレクションをすべて削除できます。
私の途方もなく長い質問を読んでくれた人に感謝します。私の質問が将来誰かに役立つことを願っています。
他のヒント
問題は次の2、3行にあります:
Collection<SubStuff> mergedSubStuffs = new ArrayList<SubStuff>(SubStuffs.size());
...
stuff.setSubStuffCollection(mergedSubStuffs);
JPAは、新しいコレクションを完全な新しいサブエンティティのセットと見なし、常にそれらを挿入します。 Stuffエンティティ内でSubStuffの元のコレクションを操作し続けると問題ありません。