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

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

Другие вопросы по тегам