带有Hibernate 4和ManyToOne级联的IllegalStateException

Cla*_*den 23 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
}
Run Code Online (Sandbox Code Playgroud)

组件对象:

@Entity
public class Component implements Serializable {

    @Id
    private String name;

    //again, default constructor, getter, setter, equals and hashCode
}
Run Code Online (Sandbox Code Playgroud)

我想用以下代码坚持下去:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

虽然这适用于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)
Run Code Online (Sandbox Code Playgroud)

数据库后端是h2(但hsqldb或derby也是如此).我究竟做错了什么?

Tob*_*obb 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

  • 在我的例子中,问题是我有一个与实体管理器分离的对象图,序列化,然后反序列化并再次合并到实体管理器.但序列化/反序列化使得过去对同一对象的引用引用了不同的对象.不确定您是否有相同的场景,但解决方案可能是确保每个持久对象(由数据库ID标识)是同一个对象(相同的对象ID).就我而言,域名对于这样的解决方案来说过于复杂,因此降级Hibernate版本成为最安全的解决方案. (2认同)

Ric*_*llo 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键相等来实现equals()和hashCode().业务键相等性意味着equals()方法仅比较构成业务键的属性.它是一个用于标识我们的实例的键.现实世界(自然候选键)"

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


Ido*_*.Co 0

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

看来您给了他们相同的 ID。

    Component c1 = new Component();
    c1.setName("comp");
    Component c2 = new Component();
    c2.setName("comp");
Run Code Online (Sandbox Code Playgroud)

这可能会解决你的问题。