web-dev-qa-db-ja.com

Hibernate 4およびManyToOneカスケードを伴うIllegalStateException

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

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でも同じことが起こります)。何が悪いのですか?

23
Clayton Louden

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

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

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

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

27
Tobb

ここでも同じです。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()メソッドがビジネスキーを形成するプロパティのみを比較することを意味します。実世界でインスタンスを識別するキー(自然な候補キー) "

つまり、equals()実装の一部としてIDを使用しないでください。

5

アイテムとコンポーネントの関係は単方向または双方向ですか?双方向の場合は、Cascade.MERGE Itemに戻る呼び出し。

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

4
adam

同じ例外(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;

理由は分からないけど

2
Supun Sameera

Componentクラスの@GeneratedValueの下に@Idアノテーションを追加してみてください。そうしないと、2つの異なるインスタンスが同じIDを取得して衝突する可能性があります。

同じIDを与えているように見えます。

    Component c1 = new Component();
    c1.setName("comp");
    Component c2 = new Component();
    c2.setName("comp");

それで問題が解決するかもしれません。

0
Ido.Co

私は同じ問題を抱えていましたが、解決しました。上記の回答で問題を解決できる可能性がありますが、特に実装されている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オブジェクトをmerging()し、Bookオブジェクトをmerging()していました。しかし、本のコレクションはAuthorオブジェクトのコンポーネントなので、2度保存しようとしていました。私の解決策は、カスケードタイプを次のように変更することでした。

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

そして

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

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

これが役立つ/理にかなっていると思います。

0
Alex Plouff

jboss EAP 6.を使用している場合はjboss 7.1.1に変更します。これはjboss EAP 6のバグです https://access.redhat.com/documentation/en-US/JBoss_Enterprise_Application_Platform/6.3/ html/6.3.0_Release_Notes/ar01s07s03.html

0
Ekici

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

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

c2 = itemDao.find("comp", Component.class); //or something like this AFTER the c1 has been persisted
0
Frank Orellana

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

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

0