IllegalStateException с Hibernate 4 и ManyToOne каскадом
У меня есть эти два класса
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
}
Компонентный объект:
@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). Что я делаю неправильно?
Ответы
Ответ 1
У меня была та же проблема, и вот что я нашел:
Метод слияния пересекает график объекта, который вы хотите сохранить, и для каждого объекта на этом графе он загружает его из базы данных, поэтому он имеет пару (постоянный объект, отдельный объект) для каждого объекта в граф, где отдельный объект - это объект, который будет храниться, и постоянная сущность получена из базы данных. (В методе, а также в сообщении об ошибке постоянный объект известен как "копия" ). Затем эти пары помещаются в две карты: одна с постоянным сущностью в качестве ключа, а отдельный объект - как значение, а другой - с отсоединенным объектом как ключом и постоянным объектом как значением.
Для каждой такой пары entites она проверяет эти карты, чтобы увидеть, совпадает ли постоянный объект с тем же отделяемым объектом, что и раньше (если он уже был посещен), и наоборот. Эта проблема возникает, когда вы получаете пару сущностей, где получение get с постоянным объектом возвращает значение, но получение с другой карты с отключенным объектом возвращает null, что означает, что вы уже связали постоянный объект с отсоединенным объект с другим хэш-кодом (в основном идентификатор объекта, если вы не переопределили метод hashcode).
TL; DR, у вас есть несколько объектов с разными идентификаторами объектов /hashcode, но с тем же идентификатором стойкости (таким образом, ссылка на один и тот же постоянный объект). Это, видимо, больше не допускается в новых версиях 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
Ответ 2
То же самое здесь, проверьте свой метод equals(). Скорее всего, это плохо реализовано.
Изменить:
Я проверил, что операция слияния не будет работать, если вы неправильно реализуете методы Entity equals() и hashCode().
Вы должны следовать этим рекомендациям для реализации equals() и hashCode():
http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html/ch04.html#persistent-classes-equalshashcode
"Рекомендуется использовать equals() и hashCode(), используя равенство Business Key. Равенство бизнес-ключа означает, что метод equals() сравнивает только свойства, которые образуют бизнес-ключ. Это ключ, который идентифицирует наши экземпляр в реальном мире (естественный ключ кандидата)
Это означает: вы НЕ должны использовать свой идентификатор как часть реализации equals()!
Ответ 3
Являются ли ваши отношения между элементом и компонентом однонаправленным или двунаправленным? Если он двунаправленный, убедитесь, что у вас нет вызовов Cascade.MERGE
, возвращающихся к элементу.
В основном, новая версия Hibernate имеет карту сущностей, которая содержит список всех вещей, которые необходимо объединить, на основе вызова merge() он вызовет слияние, а затем переместится на следующий, но сохранит на карте, он выдает указанную выше ошибку "Копия сущности уже была назначена другому сущности", когда она сталкивается с предметом, который уже был рассмотрен. Мы нашли в нашем приложении, когда мы разместили эти "восходящие" слияния в графе объектов, т.е. на двунаправленных ссылках он исправил слияние.
Ответ 4
Имело то же исключение (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;
Я не знаю причины, хотя
Ответ 5
Попробуйте добавить аннотацию @GeneratedValue
в @Id
в классе Component.
в противном случае два разных экземпляра могут получить один и тот же идентификатор и столкнуться.
Это означает, что вы даете им одинаковый идентификатор.
Component c1 = new Component();
c1.setName("comp");
Component c2 = new Component();
c2.setName("comp");
Это может решить вашу проблему.
Ответ 6
Если имя является идентификатором, почему вы создаете два объекта с одинаковым идентификатором? вы можете использовать объект c1 во всем коде.
Если это только пример и вы создаете объект c2 в другой части кода, то вам не следует создавать новый объект, но загружать его из базы данных:
c2 = itemDao.find("comp", Component.class); //or something like this AFTER the c1 has been persisted
Ответ 7
В соответствии с логикой в EventCache все сущности в графе объектов должны быть уникальными.
Итак, лучшим решением (или он работает?) Является удаление каскада в MyItem для Component. И объединить компонент отдельно, если это действительно необходимо - я бы поспорил, что в 95% случаев Компонент не должен объединяться в соответствии с бизнес-логикой.
С другой стороны - мне очень интересно знать настоящие мысли за этим ограничением.
Ответ 8
если вы используете 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
Ответ 9
У меня была такая же проблема, просто она была решена. Хотя приведенные выше ответы могут решить проблему, я не согласен с некоторыми из них, особенно изменяя реализованные методы equlas() и hashcode(). Однако я чувствую, что мой ответ усиливает ответы @Tobb и @Supun s. (Ы).
На моей стороне (дочерняя сторона) у меня была
@OneToMany(mappedBy = "authorID", cascade =CascadeType.ALL, fetch=FetchType.EAGER)
private Colllection books;
И с моей стороны (родительская сторона)
@ManyToOne(cascade =CascadeType.ALL)
private AuthorID authorID;
Прочитав отличный верный ответ, предоставленный @Tobb, и немного подумав, я понял, что аннотации не имеют смысла. То, как я это понимаю (в моем случае), я объединил() объект Author и сменил() объект книги. Но поскольку коллекция книг является компонентом объекта Author, она пыталась сохранить ее дважды.
Моим решением было изменить типы каскадов на:
@OneToMany(mappedBy = "authorID", cascade =CascadeType.PERSIST, fetch=FetchType.EAGER)
private Collection bookCollection;
и
@ManyToOne(cascade =CascadeType.MERGE)
private AuthorID authorID;
Короче говоря, перенесите родительский объект и слейте дочерний объект.
Надеюсь, что это поможет/имеет смысл.