IllegalStateException with Hibernate 4 and ManyToOne cascading

孤者浪人 提交于 2019-11-26 14:19:55

问题


I've got those two classes

MyItem Object:

@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
}

Component Object:

@Entity
public class Component implements Serializable {

    @Id
    private String name;

    //again, default constructor, getter, setter, equals and hashCode
}

And I'm tring to persist those with the following code:

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);
    }
}

While this works fine with Hibernate 3.6, Hibernate 4.1.3 throws

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)

Database backend is h2 (but the same happens with hsqldb or derby). What am I doing wrong?


回答1:


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




回答2:


Same here, check your equals() method. Most probably is badly implemented.

Edit: I have verified that a merge operation will not work if you don't implement your Entity's equals() and hashCode() methods correctly.

You should follow these guidelines for implementing equals() and hashCode():

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

"It is recommended that you implement equals() and hashCode() using Business key equality. Business key equality means that the equals() method compares only the properties that form the business key. It is a key that would identify our instance in the real world (a natural candidate key)"

That means: You should NOT use your Id as part of your equals() implementation!




回答3:


Is your relationship between item and component unidirectional or bidirectional? If it's bidirectional make sure you don't have Cascade.MERGE calls going back up to Item.

Basically, the newer version of Hibernate has a entity map that contains a list of all things that need to be merged based on the call to merge() it will call merge and then move onto the next one, but keep things in the map, it will throw the error you state above "An entity copy was already assigned to a different entity" when it encounters an item that has already been dealt with. We found in our app when we located these "upward" merges in the object graph ie. on bidirectional links, it fixed the merge call.




回答4:


Had the same exception (hibernate 4.3.0.CR2) tiring to save a object which is having two copies of a child object, got fixed by, in the entity from :

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

to just,

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

i don't know the reason though




回答5:


Try adding the @GeneratedValue annotation under @Id in the Component class. otherwise two different instances might get the same id, and collide.

It seams that you are giving them the same ID.

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

That just might solve you'r problem.




回答6:


If name is the Id why are you creating two objects with the same id?? you can use the c1 object in all the code.

If that's only an example and you create the c2 object in another part of the code, then you shouldn't create a new object but load it from database:

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



回答7:


According to logic in EventCache all entities in object graph should be unique. So the best solution(or is it work around?) is to remove cascade in MyItem to Component. And merge Component separately if it's really needed - I would bet that in 95% of cases Component shouldn't been merged according to business logic.

From other hand - I really interested to know the real thoughts behind that restriction.




回答8:


if you are using jboss EAP 6.. change it to jboss 7.1.1 .this is a bug of jboss EAP 6. https://access.redhat.com/documentation/en-US/JBoss_Enterprise_Application_Platform/6.3/html/6.3.0_Release_Notes/ar01s07s03.html




回答9:


I had the same problem, just solved it. While the above answers may solve the problem I disagree with a few of them, especially altering the implemented equlas() and hashcode() methods. However I feel like my answer reinforces @Tobb and @Supun s' answer(s).

On my Many side (child side) I had

 @OneToMany(mappedBy = "authorID", cascade =CascadeType.ALL, fetch=FetchType.EAGER)
 private Colllection books;

And on my one side (parent side)

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

After reading the excellent top answer provided by @Tobb and a little bit of thinking I realized the annotations didn't make sense. The way I understand it (in my case) I was merging() the Author object and merging() the book Object. But because the book collection is a component of the Author object it was trying to save it twice. My solution was to change the cascade types to:

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

and

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

To make a long story short, Persist the parent object and merge the child object.

Hope this helps/makes sense.



来源:https://stackoverflow.com/questions/10550511/illegalstateexception-with-hibernate-4-and-manytoone-cascading

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!