23

私はそれらの2つのクラスを持っています

MyItem オブジェクト:

@Entity
public class MyItem implements Serializable {

    @Id
    private Integer id;
    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    private Component defaultComponent;
    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    private Component masterComponent;

    //default constructor, getter, setter, equals and hashCode
}

コンポーネント オブジェクト:

@Entity
public class Component implements Serializable {

    @Id
    private String name;

    //again, default constructor, getter, setter, equals and hashCode
}

そして、私は次のコードでそれらを永続化しようとしています:

public class Test {

    public static void main(String[] args) {
        Component c1 = new Component();
        c1.setName("comp");
        Component c2 = new Component();
        c2.setName("comp");
        System.out.println(c1.equals(c2)); //TRUE

        MyItem item = new MyItem();
        item.setId(5);
        item.setDefaultComponent(c1);
        item.setMasterComponent(c2);

        ItemDAO itemDAO = new ItemDAO();
        itemDAO.merge(item);
    }
}

これは Hibernate 3.6 では問題なく動作しますが、Hibernate 4.1.3 ではスローされます。

Exception in thread "main" java.lang.IllegalStateException: An entity copy was already assigned to a different entity.
        at org.hibernate.event.internal.EventCache.put(EventCache.java:184)
        at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:285)
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
        at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:914)
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:896)
        at org.hibernate.engine.spi.CascadingAction$6.cascade(CascadingAction.java:288)
        at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:380)
        at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:323)
        at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:208)
        at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:165)
        at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:423)
        at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:213)
        at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:282)
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:76)
        at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:904)
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:888)
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:892)
        at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:874)
        at sandbox.h4bug.Test$GenericDAO.merge(Test.java:79)
        at sandbox.h4bug.Test.main(Test.java:25)

データベースのバックエンドは h2 です (ただし、hsqldb または derby でも同じことが起こります)。私は何を間違っていますか?

4

9 に答える 9

27

私は同じ問題を抱えていましたが、これが私が見つけたものです:

merge メソッドは、格納するオブジェクトのグラフを走査し、このグラフ内の各オブジェクトをデータベースからロードするため、グラフ内のオブジェクトごとに (永続エンティティ、切り離されたエンティティ) のペアがあります。切り離されたエンティティは保存されるエンティティであり、永続エンティティはデータベースから取得されます。(メソッドとエラー メッセージでは、永続エンティティは「コピー」として知られています)。次に、これらのペアは 2 つのマップに配置されます。1 つは永続エンティティをキーとして、分離されたエンティティを値として持ち、もう 1 つは分離されたエンティティをキーとして、永続エンティティを値として持ちます。

このようなエンティティのペアごとに、これらのマップをチェックして、永続エンティティが以前と同じ切り離されたエンティティにマップされているかどうか (既にアクセスされている場合)、およびその逆かどうかを確認します。この問題は、永続エンティティで get を実行すると値が返されるエンティティのペアを取得するときに発生しますが、切り離されたエンティティで他のマップから get を実行すると null が返されます。これは、永続エンティティを切り離されたエンティティと既にリンクしていることを意味します。異なるハッシュコードを持つエンティティ (hashcode-method をオーバーライドしていない場合、基本的にはオブジェクト識別子)。

TL;DR、異なるオブジェクト識別子/ハッシュコードを持つ複数のオブジェクトがありますが、永続識別子は同じです (したがって、同じ永続エンティティを参照しています)。これは、Hibernate4 の新しいバージョン (私が知る限り 4.1.3.Final 以降) では許可されないようです。

エラーメッセージはあまり良くありません。実際に言うべきことは次のようなものです:

A persistent entity has already been assigned to a different detached entity

また

Multiple detached objects corresponding to the same persistent entity

于 2013-03-12T11:25:15.483 に答える
5

ここでも同じです。equals() メソッドを確認してください。ほとんどの場合、実装が不適切です。

編集:エンティティの equals() および hashCode() メソッドを正しく実装しないと、マージ操作が機能しないことを確認しました。

equals() および hashCode() を実装するには、次のガイドラインに従う必要があります。

http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html/ch04.html#persistent-classes-equalshashcode

ビジネス キーの等価性を使用して equals() と hashCode() を実装することをお勧めします。ビジネス キーの等価性とは、equals() メソッドがビジネス キーを形成するプロパティのみを比較することを意味します。実世界 (自然な候補キー)"

つまり: Id を equals() 実装の一部として使用しないでください!

于 2012-08-21T21:00:13.347 に答える
4

アイテムとコンポーネントの関係は一方向ですか、それとも双方向ですか? Cascade.MERGE双方向の場合は、呼び出しがアイテムに戻っていないことを確認してください。

基本的に、Hibernate の新しいバージョンには、merge() の呼び出しに基づいてマージする必要があるすべてのもののリストを含むエンティティ マップがあり、merge を呼び出してから次のものに移動しますが、マップ内のものを保持します。既に処理されたアイテムに遭遇すると、上記の「エンティティのコピーは既に別のエンティティに割り当てられています」というエラーがスローされます。オブジェクト グラフでこれらの「上向き」マージを見つけたときに、アプリで見つけました。双方向リンクでは、マージ呼び出しが修正されました。

于 2012-08-30T01:29:22.840 に答える
2

同じ例外 (hibernate 4.3.0.CR2) が発生し、子オブジェクトの 2 つのコピーを持つオブジェクトを保存するのが面倒になり、次のエンティティで修正されました。

@OneToOne(cascade = CascadeType.MERGE)
private User reporter;
@OneToOne(cascade = CascadeType.MERGE)
private User assignedto;

ただ、

@OneToOne
private User reporter;
@OneToOne
private User assignedto;

理由はわからないけど

于 2013-12-27T09:24:15.790 に答える
0

私は同じ問題を抱えていましたが、それを解決しました。上記の回答で問題が解決する可能性がありますが、特に実装されている equlas() および hashcode() メソッドを変更することについて、私はそれらのいくつかに同意しません。しかし、私の答えは@Tobbと@Supunの答えを補強しているように感じます。

私の多くの側(子側)で私は持っていました

 @OneToMany(mappedBy = "authorID", cascade =CascadeType.ALL, fetch=FetchType.EAGER)
 private Colllection books;

そして、私の一方(親側)に

 @ManyToOne(cascade =CascadeType.ALL)
 private AuthorID authorID;

@Tobb によって提供された優れた最高の回答を読み、少し考えた後、注釈が意味をなさないことに気付きました。私がそれを理解する方法(私の場合)は、 Author オブジェクトをマージ()し、本のオブジェクトをマージ()していました。しかし、本のコレクションは Author オブジェクトのコンポーネントであるため、2 回保存しようとしていました。私の解決策は、カスケード タイプを次のように変更することでした。

  @OneToMany(mappedBy = "authorID", cascade =CascadeType.PERSIST, fetch=FetchType.EAGER)
  private Collection bookCollection;

 @ManyToOne(cascade =CascadeType.MERGE)
 private AuthorID authorID;

簡単に言うと、親オブジェクトを永続化し、子オブジェクトをマージします。

これが役立つ/意味があることを願っています。

于 2015-11-09T02:55:41.600 に答える
0

EventCache のロジックによれば、オブジェクト グラフ内のすべてのエンティティは一意である必要があります。したがって、最善の解決策 (または回避策はありますか?) は、MyItem から Component へのカスケードを削除することです。本当に必要な場合は、Component を個別にマージします。95% のケースでは、Component はビジネス ロジックに従ってマージされるべきではありません。

一方で、その制限の背後にある本当の考えを知りたいと思っています。

于 2013-03-12T10:47:12.930 に答える
0

name が Id の場合、同じ ID を持つ 2 つのオブジェクトを作成するのはなぜですか?? すべてのコードで c1 オブジェクトを使用できます。

これが単なる例であり、コードの別の部分で c2 オブジェクトを作成する場合、新しいオブジェクトを作成するのではなく、データベースからロードする必要があります。

c2 = itemDao.find("comp", Component.class); //or something like this AFTER the c1 has been persisted
于 2012-08-26T05:30:32.123 に答える
0

jboss EAP 6 を使用している場合は、jboss 7.1.1 に変更してください。これは jboss EAP 6 のバグです 。 0_Release_Notes/ar01s07s03.html

于 2014-07-22T13:24:38.723 に答える