Добавление дочернего объекта дважды к родителю, вызывающее NonUniqueObjectException - PullRequest
3 голосов
/ 19 ноября 2010

Допустим, у меня есть экран, который показывает животных на моей ферме.Если пользователь хочет добавить животных, он может нажать кнопку «Добавить», чтобы перейти на другой экран со списком имен животных.Если пользователь выберет «собака», мое приложение запросит базу данных и вернет мне объект «Собака», и я добавлю его в свою коллекцию животных в своей сущности «Ферма».

В описанной выше ситуации, если бы я должен был сохранить сущность фермы, ферма была бы успешно сохранена, как и ожидалось.

Однако, если пользователь добавляет «собаку» в ферму, а затемрешает снова добавить «собаку», сохранение сущности фермы приводит к:

org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session

Теперь это имеет смысл, очевидно, потому что пользователь дважды выбрал «собаку», в результате чего мое приложение копает до двух экземпляров "собаки".Как лучше всего справиться с такой ситуацией?

Редактировать: Позвольте мне уточнить, животные не привязаны к ферме напрямую.Ферма будет содержать коллекцию AnimalShelters, каждый из которых может содержать животное.AnimalShelters уникальны и имеют собственные идентификаторы.Я полагаю, вы могли бы думать о «собаке» как о привилегированной собаке, которая могла бы смириться между несколькими укрытиями.

Редактировать: Вот последовательность действий:

  • 1-й экран показывает ферму.AnimalShelters отсутствуют.
  • Пользователь нажимает кнопку «Добавить», чтобы добавить AnimalShelter
  • На новом экране есть таблица животных, которые существуют в базе данных
  • Пользовательвыбирает собаку, которая найдена в базе данных, используя 'find' [Это делается в транзакции]
  • Создается новый объект AnimalShelter
  • Собака установлена ​​на новом AnimalShelter
  • Затем пользователь выбирает «добавить» еще один AnimalShelter, содержащий ту же собаку (повторяя предыдущие 5 шагов)

Редактировать: Может быть, объяснение этого в псевдокоде может сделатьМоя проблема немного прояснилась:

  • Открытая сессия
  • Начать транзакцию
  • Animal animal1 = session.get (Animal.class, 1L);
  • Совершить транзакцию
  • Связать animal1 с новым AnimalShelter, который мы связываем с фермой
  • Начать транзакцию
  • Animal animal2 = session.get (Animal.class, 1L); // это возвращает другой экземпляр того же животного по сравнению с животным в строке 3, что имеет смысл, поскольку мы находимся в другой транзакции.Но есть ли способ получить это, чтобы дать мне тот же экземпляр Animal, даже если я нахожусь в другой транзакции?
  • Подтверждение транзакции
  • Свяжите animal2 с новым AnimalShelter, на который мы ссылаемсяферма // Теперь есть два приюта для животных, которые я хочу указать на одно и то же животное
  • Закрыть сессию

Редактировать: Вот схема:

+---------------+
| Farm          |
+---------------+
| Id (pk)       |
| Name          |
+---------------+

+---------------+
| AnimalShelter |
+---------------+
| Id (pk)       |
| AnimalId      |
| FarmId        |
+---------------+

+---------------+
| Animal        |
+---------------+
| Id (pk)       |
| Name          |
+---------------+

Редактировать: Stacktrace:

org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.spike.model.Animal#1]
at org.hibernate.engine.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:637)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:305)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:246)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:112)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:677)
at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:669)
at org.hibernate.engine.CascadingAction$5.cascade(CascadingAction.java:252)
at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:392)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:335)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:204)
at org.hibernate.engine.Cascade.cascade(Cascade.java:161)
at org.hibernate.event.def.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:451)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:288)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:144)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:117)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:677)
at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:669)
at org.hibernate.engine.CascadingAction$5.cascade(CascadingAction.java:252)
at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:392)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:335)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:204)
at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:425)
at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:362)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:338)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:204)
at org.hibernate.engine.Cascade.cascade(Cascade.java:161)
at org.hibernate.engine.Cascade.cascade(Cascade.java:127)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.cascadeOnUpdate(DefaultSaveOrUpdateEventListener.java:376)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:350)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:246)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:112)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:677)
at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:669)
at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:665)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:344)
at $Proxy14.saveOrUpdate(Unknown Source)
at com.spike.ui.SaveFarmActionListener.actionPerformed(SaveFarmActionListener.java:29)

Редактировать: Из комментариев, которые я получил, мой потоктеперь выглядит так, что все еще вызывает то же исключение:

  • Открыть сеанс
  • Начать транзакцию
  • Animal animal1 = session.get (Animal.class, 1л);
  • Свяжите animal1 с новым AnimalShelter, который мы связываем с Фермой
  • Animal animal2 = session.get (Animal.class, 1L); // теперь это возвращает тот же экземпляр Animal, что и выше.
  • Link animal2 с новым AnimalShelter, который мы связываем с фермой // Теперь есть два приюта для животных, которые я хочууказать на то же самое животное
  • совершить транзакцию Это утверждение все еще вызывает исключение, несмотря на то, что я вижу, что оно указывает на тот же экземпляр Animal
  • Закрытие сессии

Редактировать: Интересно, что теперь, когда у меня есть все в одной транзакции, кажется, что теперь происходит сбой с тем же исключением, даже если я добавляю только одно животное.Причина этого, по-видимому, заключается в том, что, когда я перехожу на второй экран, он запрашивает все доступные животные - одним из них, конечно же, является собака.Когда я добавляю только один AnimalShelter, содержащий Dog, и пытаюсь сохранить, он выдаст то же исключение, потому что я предполагаю, что он был загружен в сессию уже на втором экране, где отображались все доступные животные.

Ответы [ 3 ]

1 голос
/ 20 ноября 2010

Исключение говорит само за себя:

org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session

Похоже, что произошло то, что вы вводите два объекта одного и того же класса сущности (который, я думаю, Dog?) В EntityManager, что обаиметь тот же первичный ключ.Это не разрешено

Если вы создаете новые Dog сущности (создавая новые экземпляры и вызывая em.persist() для каждого), убедитесь, что вы случайно не используете одно и то же значение первичного ключа дважды.Это то же самое, что пытаться сделать два встроенных оператора SQL INSERT с одним и тем же значением первичного ключа.Я предполагаю, что это твоя проблема. Как ваше приложение назначает новые значения первичного ключа новым сущностям? Возможно, вы обнаружите, что генерация первичного ключа не работает должным образом.

Если вы работаете с существующими сущностями Dog, тоубедитесь, что ваши сущности «управляемы», а не «отделены».Вы можете повторно присоединить отдельную сущность, вызвав на нее em.merge(), а затем поработав с возвращенным (и управляемым) экземпляром сущности из вызова merge().Я не думаю, что это ваша проблема, потому что вы, скорее всего, получили бы исключение какой-либо отдельной сущности, а не «неуникальное» исключение.

Чтобы ответить на вопрос с комментариями, оставленный наответ другого парня:

как мне убедиться, что моя ручка к объекту "собака" - это тот же объект "собака", когда я в первый раз добавил его в AnimalShelter?

Вызовите метод find () EntityManager следующим образом:

em.find(Dog.class, myDogPrimaryKeyValue)

Это вернет экземпляр Dog, который "управляется" этим конкретным EntityManager.Каждый EntityManager имеет гарантию для возврата одного и того же экземпляра Dog каждый раз, когда вы вызываете find() с одним и тем же значением первичного ключа.Просто будьте осторожны, чтобы не пытаться использовать экземпляр сущности, который был возвращен из одного EntityManager в другом EntityManager.Сущность управляется только EntityManager, который изначально вернул вам эту сущность, а не EntityManager.См. Мои комментарии выше об использовании merge() для получения управляемого экземпляра, когда вы начинаете с неуправляемого (AKA "отсоединенного") экземпляра.Вы можете узнать, управляется ли экземпляр объекта конкретным EntityManager, вызвав em.contains() объекта.

РЕДАКТИРОВАТЬ:

На основе новой информации впри редактировании вопроса возникает проблема с пересечением границ транзакции.Можете ли вы оставить транзакцию открытой в течение всей продолжительности сеанса, вместо того, чтобы выполнять фиксацию и затем создавать другую?Это, вероятно, решит вашу проблему.В качестве альтернативы вы можете использовать управляемый приложением EntityManager, чтобы ваш постоянный контекст сохранялся в течение всего сеанса.

1 голос
/ 23 ноября 2010

Я наконец получил его на работу.Просто чтобы суммировать то, что я получил из ответов и комментариев, чтобы заставить его работать:

  • При выполнении большого редактирования Фермы, все маленькие действия по поиску Животных, привязке Животных к Животному Приюту,связывание AnimalShelter с фермой было выполнено в ОДНОЙ транзакции.Это позволило несколько раз извлекать Animals и гарантировать, что один и тот же экземпляр Animal был возвращен из сеанса;
  • equals () / hashCode () вообще не были вызваны в классе сущностей Animal
  • merge () также не является частью окончательного решения
  • Сначала я попытался выполнить session.evict (...) на 2-м экране, чтобы убедиться, что поиск Животных не вызывает исключение уникальности
  • Наряду с транзакцией, другой большой частью проблемы, которая фактически вызывала исключение, был факт, что у меня были двунаправленные отношения в моей модели.У меня был каскадный тип ALL, установленный на этих отношениях.Каскадный тип ALL имел смысл в большинстве случаев, когда это была связь между родителем и потомком.Но у меня также была ссылка на родителя с каскадом ВСЕ.Как только я удалил эти бесполезные (и в конечном итоге) некорректные каскадные атрибуты в аннотациях сопоставления, исключение исчезло!
1 голос
/ 19 ноября 2010

Тогда собака должна иметь достаточно привилегий, чтобы установить отношения с AnimalShelter. Точно, между Animal и AnimalShelter.

должна быть взаимосвязь «один ко многим».

Например, equals().

public boolean equals(Object that) {
    if ( this == that ) return true;
    if ( !(that instanceof Dog) ) return false;
    Dog dog = (Dog)that;
    // Assuming id is of Long type
    return this.id.longValue() == dog.id.longValue();
}

И не забудьте переопределить hashcode().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...