带有Hibernate 4和ManyToOne级联的IllegalStateException

时间:2012-05-11 11:30:00

标签: java hibernate jpa many-to-one hibernate-cascade

我有两个班级

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也是如此)。我做错了什么?

9 个答案:

答案 0 :(得分:27)

我遇到了同样的问题,这就是我发现的:

合并方法遍历您要存储的对象的图形,并且对于此图形中的每个对象,它从数据库加载它,因此它具有一对(持久实体,分离实体),用于图,其中分离的实体是将要存储的实体,而持久化实体是从数据库中获取的。 (在该方法中,以及在错误消息中,持久性实体称为“复制”)。然后将这些对放在两个映射中,一个以持久实体为键,分离实体为值,一个以分离实体为键,持久实体为值。

对于每个这样的entites,它会检查这些映射,以查看持久性实体是否映射到与之前相同的分离实体(如果它已被访问过),反之亦然。当你得到一对实体,其中使用持久化实体获取get返回值时,会发生此问题,但是从其他映射获取,并且分离的实体返回null,这意味着您已经将持久实体与已分离的实体链接具有不同哈希码的实体(如果您没有覆盖哈希码方法,则基本上是对象标识符)。

TL; DR,您有多个具有不同对象标识符/哈希码的对象,但具有相同的持久性标识符(因此引用相同的持久性实体)。在较新版本的Hibernate4中显然不再允许这样做(4.1.3.Final以及我所能说的最高版本)。

错误消息不是很好imo,它真正应该说的是:

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

Multiple detached objects corresponding to the same persistent entity

答案 1 :(得分:5)

在这里,检查你的equals()方法。很可能很难实现。

编辑: 我已经验证如果你没有正确实现你的Entity的equals()和hashCode()方法,合并操作将不起作用。

您应该遵循以下准则来实现equals()和hashCode():

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

“建议您使用Business key equality实现equals()和hashCode()。Business key equality意味着equals()方法只比较构成业务键的属性。它是一个关键将识别我们在现实世界中的实例(自然候选键)“

这意味着:您不应将您的Id用作equals()实现的一部分!

答案 2 :(得分:4)

项目和组件单向双向之间的关系是什么?如果它是双向的,请确保没有Cascade.MERGE次呼叫返回到项目。

基本上,较新版本的Hibernate有一个实体映射,其中包含需要根据对merge()的调用合并的所有内容的列表,它将调用merge然后移动到下一个,但保留在地图上,当遇到已经处理过的项目时,它会抛出您在上面说明的错误“实体副本已经分配给不同的实体”。当我们在对象图中找到这些“向上”合并时,我们在应用程序中找到了ie。在双向链接上,它修复了合并调用。

答案 3 :(得分:2)

有相同的异常(hibernate 4.3.0.CR2)累人来保存一个有两个子对象副本的对象, 在实体中得到了修复 来自:

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

到了,

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

我不知道原因

答案 4 :(得分:0)

尝试在Component类的@GeneratedValue下添加@Id注释。 否则两个不同的实例可能会获得相同的ID,并发生冲突。

接缝是你给他们相同的身份证。

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

这可能会解决你的问题。

答案 5 :(得分:0)

如果name是Id,为什么要创建两个具有相同id的对象?你可以在所有代码中使用c1对象。

如果这只是一个例子并且您在代码的另一部分中创建了c2对象,那么您不应该创建新对象,而是从数据库加载它:

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

答案 6 :(得分:0)

根据EventCache中的逻辑,对象图中的所有实体都应该是唯一的。 那么最好的解决方案(或者它可以解决吗?)是将MyItem中的级联移除到Component。如果确实需要,请单独合并Component - 我敢打赌,在95%的情况下,组件不应该根据业务逻辑进行合并。

另一方面 - 我真的很想知道这种限制背后的真实想法。

答案 7 :(得分:0)

如果您正在使用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

答案 8 :(得分: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对象的一个​​组件,所以它试图将它保存两次。 我的解决方案是将级联类型更改为:

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

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

简而言之,坚持父对象并合并子对象。

希望这有助于/有意义。