IllegalStateException с каскадированием Hibernate 4 и ManyToOne

У меня есть эти два класса

Объект 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). Что я делаю неправильно?


person Clayton Louden    schedule 11.05.2012    source источник


Ответы (9)


У меня была такая же проблема, и вот что я нашел:

Метод слияния проходит через граф объекта, который вы хотите сохранить, и для каждого объекта в этом графе он загружает его из базы данных, поэтому он имеет пару (постоянный объект, отсоединенный объект) для каждого объекта в графе, где отдельная сущность — это сущность, которая будет храниться, а постоянная сущность будет получена из базы данных. (В методе, а также в сообщении об ошибке постоянный объект известен как «копия»). Затем эти пары помещаются в две карты: одна с постоянной сущностью в качестве ключа и отсоединенной сущностью в качестве значения, а другая с отсоединенной сущностью в качестве ключа и постоянной сущностью в качестве значения.

Для каждой такой пары сущностей он проверяет эти сопоставления, чтобы увидеть, сопоставляется ли постоянная сущность с той же отсоединенной сущностью, что и раньше (если она уже была посещена), и наоборот. Эта проблема возникает, когда вы получаете пару сущностей, где получение с постоянной сущностью возвращает значение, но получение с другой карты с отсоединенной сущностью возвращает значение null, что означает, что вы уже связали постоянную сущность с отсоединенной сущностью. объект с другим хэш-кодом (в основном идентификатор объекта, если вы не переопределили метод хэш-кода).

TL;DR, у вас есть несколько объектов с разными идентификаторами объекта/хэш-кодом, но с одним и тем же идентификатором постоянства (таким образом, ссылаясь на один и тот же постоянный объект). По-видимому, это больше не разрешено в более новых версиях Hibernate4 (4.1.3.Final и выше, насколько я мог судить).

Сообщение об ошибке не очень хорошее imo, на самом деле оно должно говорить что-то вроде:

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

or

Multiple detached objects corresponding to the same persistent entity

person Tobb    schedule 12.03.2013
comment
Проблема все еще существует с версией 4.1.10 Final. Однако возврат к версии 4.1.2 Final работает. - person Zaki; 29.05.2013
comment
Хорошо, я не могу вспомнить, какие версии я пробовал, кроме 4.1.10.Final и 4.1.1.Final (к которой я в конечном итоге вернулся), но если 4.1.2.Final работает, я обновлю свой ответ. отразить это. - person Tobb; 29.05.2013
comment
Отличное описание ошибки. Любые мысли о том, что мы можем сделать по этому поводу? - person Webnet; 03.10.2013
comment
В моем случае проблема заключалась в том, что у меня был граф объектов, который был отсоединен от менеджера сущностей, сериализован, затем десериализован и снова объединен с менеджером сущностей. Но сериализация/десериализация сделала то, что раньше было ссылками на один и тот же объект, ссылается на разные объекты. Не уверен, что у вас такой же сценарий, но решение может состоять в том, чтобы убедиться, что каждый постоянный объект (идентифицируемый идентификатором базы данных) является одним и тем же объектом (тот же идентификатор объекта). В моем случае домен был слишком сложным для такого решения, поэтому самым безопасным решением стало понижение версии Hibernate. - person Tobb; 03.10.2013
comment
Если вы не сериализуете/десериализуете, то исключение может указывать на то, что код работает не так, как должен. Итак, выясните, какой постоянный объект представлен несколькими (java) объектами и почему. Если это по приемлемой причине, и ее нельзя обойти простым способом, используйте понижение версии в качестве крайней меры. - person Tobb; 03.10.2013
comment
Это все еще в силе? Я использую hibernate 4.1.9-Final, и две карты, упомянутые выше, являются IdentityHashMap, поэтому хэш-коды/равные в классах сущностей не учитываются при вставке/извлечении из этих карт. Когда вызывается put(K,V), хэш вычисляется путем вызова System.identityHashCode(obj), который обходит любую переопределенную реализацию hashCode(), поэтому фактически не имеет значения, как был реализован hashCode/equals. - person Paul; 27.01.2016
comment
Я не уверен, но из того, что я понимаю из вашего описания, это звучит так, как будто это даже более правильно, а это означает, что вы не можете обойти ошибку, добавив свой собственный equals/hashcode. Теперь должно быть 1-1 между идентификаторами объектов и идентификаторами базы данных в графе объектов, чтобы слияние не вызывало такое исключение. - person Tobb; 27.01.2016

То же самое здесь, проверьте свой метод equals(). Скорее всего плохо реализовано.

Изменить: я убедился, что операция слияния не будет работать, если вы неправильно реализуете методы equals() и hashCode() вашего Entity.

Вы должны следовать этим рекомендациям по реализации equals() и hashCode():

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

"Рекомендуется реализовать equals() и hashCode() с использованием равенства бизнес-ключей. Равенство бизнес-ключей означает, что метод equals() сравнивает только свойства, формирующие бизнес-ключ. Это ключ, который идентифицирует наш экземпляр в реальном мире (естественный ключ-кандидат)"

Это означает: вы НЕ должны использовать свой идентификатор как часть реализации equals()!

person Ricardo Arguello    schedule 21.08.2012
comment
Дополнительная информация: blog.andrewbeacock.com/2008 /08/ - person Ricardo Arguello; 23.08.2012

Ваша связь между элементом и компонентом однонаправленная или двунаправленная? Если он двунаправленный, убедитесь, что у вас нет Cascade.MERGE вызовов, возвращающихся к Item.

По сути, более новая версия Hibernate имеет карту сущностей, которая содержит список всех вещей, которые необходимо объединить, на основе вызова merge(), который вызовет слияние, а затем перейдет к следующему, но сохранит вещи на карте, он выдаст ошибку, которую вы указали выше: «Копия объекта уже была назначена другому объекту», когда он встретит элемент, с которым уже работали. Мы обнаружили в нашем приложении, когда нашли эти «восходящие» слияния в графе объектов, т.е. на двунаправленных ссылках исправлен вызов слияния.

person adam    schedule 30.08.2012

Было то же исключение (спящий режим 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;

хотя я не знаю причину

person Supun Sameera    schedule 27.12.2013

Попробуйте добавить аннотацию @GeneratedValue под @Id в классе Component. в противном случае два разных экземпляра могут получить один и тот же идентификатор и столкнуться.

Похоже, вы даете им один и тот же идентификатор.

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

Это может решить вашу проблему.

person Ido.Co    schedule 11.05.2012
comment
К сожалению, оба идентификатора не генерируются базой данных, а устанавливаются явно. - person Clayton Louden; 11.05.2012
comment
Ой, я имел в виду класс Component. вы даете им тот же идентификатор? - person Ido.Co; 11.05.2012
comment
Да, это идея, стоящая за этим. Оба компонента получают один и тот же идентификатор и равны (см. оператор равенства выше). Так что Каскад должен позаботиться об этом, верно? Вы даже можете попытаться использовать одну и ту же ссылку (например, c1) для обеих переменных (defaultComponent и masterComponent), поскольку они в любом случае равны. - person Clayton Louden; 11.05.2012
comment
Почему вы пытаетесь назначить два разных экземпляра, представляющих один и тот же объект БД, одному классу? Было бы лучше использовать один и тот же экземпляр. Я думаю, что вы злоупотребляете полем id @ БД. - person Ido.Co; 11.05.2012
comment
@RichardPena, если вы хотите, чтобы эти два экземпляра представляли один и тот же объект БД, почему бы просто не использовать один экземпляр? - person Ido.Co; 11.05.2012

Если имя - это идентификатор, почему вы создаете два объекта с одним и тем же идентификатором ?? вы можете использовать объект c1 во всем коде.

Если это только пример, и вы создаете объект c2 в другой части кода, вам не следует создавать новый объект, а загружать его из базы данных:

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

По логике EventCache все сущности в графе объектов должны быть уникальными. Таким образом, лучшее решение (или это обходной путь?) - удалить каскад из MyItem в Component. И объединяйте компонент отдельно, если это действительно необходимо - я бы поспорил, что в 95% случаев компонент не должен быть объединен в соответствии с бизнес-логикой.

С другой стороны, мне действительно интересно узнать, что стоит за этим ограничением.

person Andrei Urvantcev    schedule 12.03.2013

если вы используете 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

person Ekici    schedule 22.07.2014
comment
Проблема все еще существует с Jboss AS 7.2. - person Magnilex; 11.09.2014

У меня была такая же проблема, только что решил ее. Хотя приведенные выше ответы могут решить проблему, я не согласен с некоторыми из них, особенно с изменением реализованных методов 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;

Короче говоря, сохраните родительский объект и объедините дочерний объект.

Надеюсь, это поможет/имеет смысл.

person Alex Plouff    schedule 09.11.2015