非法状态与Hibernate 4和ManyToOne级联的异常

我有这两个类

我的项目对象:

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


答案 1

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

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

对于每对这样的实体,它会检查这些映射,以查看持久性实体是否映射到与以前相同的分离实体(如果已经访问过),反之亦然。当您获得一对实体时,会出现此问题,其中对持久实体执行 get 将返回一个值,但来自另一个映射的 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


答案 2

同样在这里,检查你的 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() 实现的一部分!


推荐