2012-05-11 3 views
23

У меня есть эти два класса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 
} 

Component Object:

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

Database Бэкэнд h2 (но то же самое происходит с HSQLDB или дерби). Что я делаю не так?

ответ

0

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

Это швы, что вы даете им одинаковый идентификатор.

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

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

+0

К сожалению, оба идентификатора не сгенерированы в базе данных, а заданы явно. –

+0

К сожалению, я имел в виду класс Component. вы даете им тот же самый идентификатор? –

+0

Да, это идея. Оба компонента имеют одинаковый идентификатор и равны (см. Выше оператор равенства). Итак, каскад должен позаботиться об этом, верно? Вы даже можете попытаться использовать ту же ссылку (например, c1) для обеих переменных (defaultComponent и masterComponent), так как они в любом случае равны. –

5

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

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

Вы должны следовать этим рекомендациям для реализации равных() и хэш-код():.

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

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

Это означает: вы НЕ должны использовать ваш Id как часть вашего equ als()!

+1

Дополнительная информация: http://blog.andrewbeacock.com/2008/08/how-to-implement-hibernate-safe-equals.html –

0

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

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

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

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

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

0

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

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

27

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

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

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

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

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

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

или

Multiple detached objects corresponding to the same persistent entity

+1

Проблема все еще существует с версией «4.1.10 Final». Однако вернется к версии «4.1.2 Final». – Zaki

+1

Хорошо, я не могу вспомнить, какие версии я попробовал помимо 4.1.10.Final и 4.1.1.Final (это тот, к которому я в конечном итоге возвращался), но если 4.1.2.Final работает, я обновлю свои ответьте, чтобы отразить это. – Tobb

+1

См. Https://hibernate.atlassian.net/browse/HHH-7605 – Zaki

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; 

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

0

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

Чтобы сделать длинную историю короткой, Упорство родительский объект и объединить дочерний объект.

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

Смежные вопросы