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). Что я делаю неправильно?
9 ответов
У меня была такая же проблема, и вот что я нашел:
Метод слияния просматривает график объекта, который вы хотите сохранить, и для каждого объекта в этом графике он загружает его из базы данных, поэтому у него есть пара (постоянная сущность, отдельная сущность) для каждого объекта в графе, где Отдельная сущность - это сущность, которая будет сохранена, а постоянная сущность получена из базы данных. (В методе, как и в сообщении об ошибке, постоянная сущность известна как "копия"). Затем эти пары помещаются в две карты, одна с постоянным объектом в качестве ключа и отсоединенным объектом в качестве значения, а другая с отсоединенным объектом в качестве ключа и постоянным объектом в качестве значения.
Для каждой такой пары объектов он проверяет эти карты, чтобы увидеть, отображается ли постоянный объект на тот же отдельный объект, что и раньше (если он уже был посещен), и наоборот. Эта проблема возникает, когда вы получаете пару сущностей, где выполнение get с персистентной сущностью возвращает значение, а get с другой карты, с отсоединенной сущностью, возвращает null, что означает, что вы уже связали персистентную сущность с отсоединенной сущностью. сущность с другим хеш-кодом (в основном идентификатор объекта, если вы не переопределили метод хеш-кода).
TL; DR, у вас есть несколько объектов с разными идентификаторами объектов / хэш-кодами, но с одним и тем же идентификатором постоянства (таким образом, ссылаясь на один и тот же постоянный объект). Это, по-видимому, больше не разрешено в более новых версиях Hibernate4 (4.1.3. Наконец и выше, что я мог сказать).
Сообщение об ошибке не очень хорошее IMO, что он действительно должен сказать что-то вроде:
A persistent entity has already been assigned to a different detached entity
или же
Multiple detached objects corresponding to the same persistent entity
То же самое здесь, проверьте ваш метод equals (). Скорее всего плохо реализовано.
Редактировать: Я убедился, что операция слияния не будет работать, если вы не правильно реализуете методы equity () и hashCode() вашего Entity.
Вы должны следовать этим рекомендациям для реализации equals () и hashCode():
http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html/ch04.html
"Рекомендуется реализовать equals () и hashCode(), используя равенство бизнес-ключей. Равенство бизнес-ключей означает, что метод equals () сравнивает только свойства, которые формируют бизнес-ключ. Это ключ, который идентифицирует наш экземпляр в реальный мир (естественный ключ-кандидат)
Это означает, что вы НЕ должны использовать свой Id как часть реализации equals ()!
Являются ли ваши отношения между элементом и компонентом однонаправленными или двунаправленными? Если это двунаправленный, убедитесь, что у вас нет Cascade.MERGE
звонки возвращаются к пункту.
По сути, более новая версия Hibernate имеет карту сущностей, которая содержит список всех вещей, которые должны быть объединены на основе вызова merge(), он вызовет слияние, а затем перейдет к следующему, но сохранит вещи на карте, он выдаст ошибку, о которой вы заявили выше "Копия сущности уже была назначена другой сущности", когда он встретит предмет, с которым уже разобрались. Мы нашли в нашем приложении, когда нашли эти "восходящие" слияния в графе объектов, т.е. в двунаправленных ссылках исправлен вызов слияния.
Было то же исключение (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;
хотя я не знаю причину
У меня была такая же проблема, только что решил. Хотя приведенные выше ответы могут решить проблему, я не согласен с некоторыми из них, особенно с изменением реализованных методов 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 и слил () книгу Object. Но поскольку коллекция книг является компонентом объекта Author, она пыталась сохранить его дважды. Мое решение состояло в том, чтобы изменить типы каскада на:
@OneToMany(mappedBy = "authorID", cascade =CascadeType.PERSIST, fetch=FetchType.EAGER)
private Collection bookCollection;
а также
@ManyToOne(cascade =CascadeType.MERGE)
private AuthorID authorID;
Короче говоря, сохраните родительский объект и объедините дочерний объект.
Надеюсь, что это помогает / имеет смысл.
Если имя является идентификатором, почему вы создаете два объекта с одинаковым идентификатором?? Вы можете использовать объект c1 во всем коде.
Если это только пример, и вы создаете объект c2 в другой части кода, то вам не следует создавать новый объект, а загружать его из базы данных:
c2 = itemDao.find("comp", Component.class); //or something like this AFTER the c1 has been persisted
Согласно логике в EventCache все сущности в графе объектов должны быть уникальными. Таким образом, лучшее решение (или это обходной путь?) Состоит в том, чтобы удалить каскад в MyItem to 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
Попробуйте добавить @GeneratedValue
аннотация под @Id
в классе компонентов. в противном случае два разных экземпляра могут получить один и тот же идентификатор и столкнуться.
Похоже, вы даете им тот же идентификатор.
Component c1 = new Component();
c1.setName("comp");
Component c2 = new Component();
c2.setName("comp");
Это может решить твою проблему.