Допустим, у меня есть экран, который показывает животных на моей ферме.Если пользователь хочет добавить животных, он может нажать кнопку «Добавить», чтобы перейти на другой экран со списком имен животных.Если пользователь выберет «собака», мое приложение запросит базу данных и вернет мне объект «Собака», и я добавлю его в свою коллекцию животных в своей сущности «Ферма».
В описанной выше ситуации, если бы я должен был сохранить сущность фермы, ферма была бы успешно сохранена, как и ожидалось.
Однако, если пользователь добавляет «собаку» в ферму, а затемрешает снова добавить «собаку», сохранение сущности фермы приводит к:
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, и пытаюсь сохранить, он выдаст то же исключение, потому что я предполагаю, что он был загружен в сессию уже на втором экране, где отображались все доступные животные.