2

JPA を Hibernate の下で使用しており、マージがうまくいかないのですが、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 のマージに関するいくつかの記事を読んだにもかかわらず、ここで何が起こっているのかについて頭を悩ませることはできません。

御時間ありがとうございます。

4

2 に答える 2

3

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 コレクションをまとめて削除できます。

私のとてつもなく長い質問を読んでくれた人に感謝します。うまくいけば、私の質問が将来誰かに役立つことを願っています。

于 2009-04-29T18:20:07.613 に答える
1

あなたの問題は、次の数行にあります。

Collection<SubStuff> mergedSubStuffs = new ArrayList<SubStuff>(SubStuffs.size());
...
stuff.setSubStuffCollection(mergedSubStuffs);

JPA は、新しいコレクションをサブエンティティの完全な新しいセットと見なし、常にそのように挿入します。Stuff エンティティ内の SubStuff の元のコレクションで作業を続ければ問題ありません。

于 2009-04-29T16:31:16.737 に答える