Подтвердить что ты не робот

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

4b9b3361

Ответ 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% случаев Компонент не должен объединяться в соответствии с бизнес-логикой.

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

Ответ 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;

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

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