带有Hibernate 4和ManyToOne级联的IllegalStateException

我有两个class

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

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

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

对于每个这样的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

同样在这里,检查你的equals()方法。 最有可能实施得很糟糕。

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

您应该遵循以下准则来实现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()实现的一部分!

项目和组件之间的关系是单向还是双向的 ? 如果它是双向的,请确保没有Cascade.MERGE调用返回到Item。

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

有相同的exception(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; 

我不知道原因

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

它接缝你给他们相同的ID。

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

这可能会解决你的问题。

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

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

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

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

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

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

我有同样的问题,只是解决了它。 虽然上面的答案可以解决这个问题,但我不同意其中的一些,尤其是改变实现的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; 

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

希望这有助于/有意义。