23

我有这两个课

我的项目对象:

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

4

9 回答 9

27

I had the same problem, and this is what I found:

The merge method traverses the graph of the object that you want to store, and for each object in this graph it loads it from the database, so it has a pair of (persistent entity, detached entity) for each object in the graph, where detached entity is the entity that is going to be stored, and persistent entity is gotten from the database. (In the method, as well as in the error message the persistent entity is known as 'copy'). Then these pairs are put in two maps, one with the persistent entity as key and the detached entity as value, and one with the detached entity as key and the persistent entity as value.

For each such pair of entites, it checks these maps, to see if the persistent entity maps to the same detached entity as before (if it has already been visited), and vice versa. This problem occurs when you get a pair of entities where doing a get with the persistent entity returns a value, but a get from the other map, with the detached entity returns null, which means that you have already linked the persistent entity with a detached entity with a different hashcode (basically the object identifier if you have not overridden the hashcode-method).

TL;DR, you have multiple objects with different object identifiers/hashcode, but with the same persistence identifier (thus referencing the same persistent entity). This is appearantly no longer allowed in newer versions of Hibernate4 (4.1.3.Final and upwards from what I could tell).

The error message is not very good imo, what it really should say is something like:

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

or

Multiple detached objects corresponding to the same persistent entity

于 2013-03-12T11:25:15.483 回答
5

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

于 2012-08-21T21:00:13.347 回答
4

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

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

于 2012-08-30T01:29:22.840 回答
2

有相同的异常(休眠 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;

我不知道原因

于 2013-12-27T09:24:15.790 回答
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

于 2014-07-22T13:24:38.723 回答
0

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

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

于 2013-03-12T10:47:12.930 回答
0

如果名称是 ID,为什么要创建两个具有相同 ID 的对象?您可以在所有代码中使用 c1 对象。

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

c2 = itemDao.find("comp", Component.class); //or something like this AFTER the c1 has been persisted
于 2012-08-26T05:30:32.123 回答
0

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

接缝你给他们相同的ID。

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

这可能会解决你的问题。

于 2012-05-11T12:43:21.993 回答
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;

长话短说,坚持父对象并合并子对象。

希望这有帮助/有意义。

于 2015-11-09T02:55:41.600 回答