假设我有一个屏幕显示我农场里的动物。如果用户想要添加动物,他们可以单击“添加”按钮,将您带到另一个列出动物名称的屏幕。如果用户选择“dog”,我的应用程序将查询数据库并返回一个Dog对象,然后将其添加到我的Farm实体中的动物集合中。
在上述情况下,如果我要保存Farm实体,则Farm将按预期成功保留。
但是,如果用户向服务器场添加“dog”,然后再决定再添加“dog”,则保存Farm实体会导致:
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session
现在这很明显是因为用户选择了“狗”两次导致我的应用程序挖掘两个“狗”实例。处理这种情况的最佳方法是什么?
编辑:让我澄清一下,动物没有直接附在农场上。农场将收藏一系列动物保护者,每个动物保护者都可以容纳一只动物。 AnimalShelters是独一无二的,并有自己的标识符。我想你可以把“狗”想象成一只可以在多个避难所之间徘徊的特权狗。
编辑:以下是工作方式的流程:
编辑:也许我用伪代码解释它可能会让我的问题更加清晰:
编辑:以下是架构:
+---------------+
| 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,它似乎也会失败并出现相同的异常。这样做的原因似乎是因为当我进入第二个屏幕时,它会查询所有可用的动物 - 其中一个当然是狗。当我只添加一个包含Dog的AnimalShelter并尝试保存时,它将抛出相同的异常,因为我假设它已经被显示所有可用动物的第二个屏幕加载到会话中。
答案 0 :(得分:1)
然后狗应该有足够的特权与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()
。
答案 1 :(得分:1)
例外说明了一切:
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中 时间?
调用EntityManager find()方法,如下所示:
em.find(Dog.class, myDogPrimaryKeyValue)
这将返回由此特定EntityManager“管理”的Dog实例。每次使用相同的主键值调用Dog
时,每个EntityManager 保证都会返回find()
的同一个实例。请注意,不要尝试使用从不同EntityManager上的一个EntityManager返回的实体实例。实体仅由最初将实体返回给您的EntityManager管理,而不是任何EntityManager。请参阅上面的评论,当您使用非托管(A.K.A.“分离”)实例启动时,使用merge()
获取托管实例。您可以通过调用实体上的em.contains()
来确定实体实例是否由特定EntityManager管理。
修改强>
根据问题编辑中的新信息,这似乎是跨越事务边界的问题。您是否可以在会话的整个持续时间内保持事务处于打开状态,而不是执行提交然后创建另一个事务?这可能会解决您的问题。或者,您可以使用应用程序管理的EntityManager,以便持久性上下文在会话期间保持不变。
答案 2 :(得分:1)
我终于得到了它的工作。只是总结一下我从答案和评论中得到的东西,以使其发挥作用: