Java 将子对象添加到父对象两次会导致UnuniqueObjectException

Java 将子对象添加到父对象两次会导致UnuniqueObjectException,java,hibernate,jpa,Java,Hibernate,Jpa,假设我有一个屏幕,显示我农场里的动物。如果用户想要添加动物,他们可以单击“添加”按钮,将您带到另一个列出动物名称的屏幕。如果用户选择“dog”,我的应用程序将查询数据库并返回dog对象,我将其添加到农场实体中的动物集合中 在上述情况下,如果我要保存Farm实体,该Farm将按预期成功持久化 但是,如果用户向服务器场添加“狗”,然后决定再次添加“狗”,则保存服务器场实体会导致: org.hibernate.NonUniqueObjectException: a different object w

假设我有一个屏幕,显示我农场里的动物。如果用户想要添加动物,他们可以单击“添加”按钮,将您带到另一个列出动物名称的屏幕。如果用户选择“dog”,我的应用程序将查询数据库并返回dog对象,我将其添加到农场实体中的动物集合中

在上述情况下,如果我要保存Farm实体,该Farm将按预期成功持久化

但是,如果用户向服务器场添加“狗”,然后决定再次添加“狗”,则保存服务器场实体会导致:

org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session
这显然是有道理的,因为用户选择了“狗”两次,导致我的应用程序挖掘了两个“狗”的实例。处理这种情况的最好办法是什么

编辑:让我澄清一下,动物不是直接附属于农场的。该农场将收藏一批动物饲养箱,每个饲养箱可饲养一只动物。动物直升机是唯一的,并且有自己的标识符。我想你可以把“狗”想象成一只特权狗,它可以在多个庇护所之间闲逛

编辑:以下是工作流程:

  • 第一个屏幕显示农场。没有动物直升机
  • 用户单击“添加”按钮添加动物直升机
  • 新屏幕上有一个数据库中存在的动物表
  • 用户使用“查找”选择在数据库中找到的狗[这在事务中完成]
  • 将创建一个新的AnimalShelter对象
  • 这只狗被安置在新的动物直升机上
  • 然后,用户选择“添加”另一个包含同一只狗的动物直升机(重复前面的5个步骤)
编辑:也许我用伪代码解释一下,可以让我的问题更清楚一些:

  • 公开会议
  • 开始交易
  • Animal animal1=会话.get(Animal.class,1L);
  • 提交事务
  • 将animal1链接到我们链接到农场的新AnimalShelter
  • 开始交易
  • 动物动物2=会话.get(Animal.class,1L);//与第3行中的动物相比,它返回同一动物的不同实例,这是有意义的,因为我们处于不同的事务中。但是有没有一种方法可以让我得到相同的动物实例,即使我在不同的交易中
  • 提交事务
  • 将animal2与我们链接到农场的新AnimalShelter链接//现在有两个动物庇护所,我想指出同一个动物
  • 闭门会议
编辑:以下是模式:

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

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

+---------------+
| Animal        |
+---------------+
| Id (pk)       |
| Name          |
+---------------+
编辑:堆栈跟踪:

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=会话.get(Animal.class,1L);
  • 将animal1链接到我们链接到农场的新AnimalShelter
  • 动物动物2=会话.get(Animal.class,1L);//现在返回与上面相同的动物实例
  • 将animal2与我们链接到农场的新AnimalShelter链接//现在有两个动物庇护所,我想指出同一个动物
  • Commit Transaction此语句仍然抛出异常,尽管我可以看到它指向同一个动物实例
  • 闭门会议

编辑:有趣的是,现在我在一个事务中拥有了所有内容,即使我只添加了一个动物,它似乎也会失败,出现同样的异常。这样做的原因似乎是因为当我进入第二个屏幕时,它会查询所有可用的动物——其中一个当然是狗。当我只添加一个包含狗的AnimalShelter并尝试保存时,它将抛出相同的异常,因为我假设它已经被第二个屏幕加载到会话中,该屏幕显示了所有可用的动物。

那么狗应该有足够的特权与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()

异常说明了一切:

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()
调用返回的(和托管的)实体实例,可以重新附加该实体。我不认为这是您的问题,因为您可能会得到某种分离实体异常,而不是“非唯一”异常

回答你在对方答案上留下的评论问题:

我如何确保我的“狗”的把柄 对象是我使用时的同一个“狗”对象 第一次将其添加到动物直升机上 时间

调用EntityManager find()方法,如下所示:

em.find(Dog.class, myDogPrimaryKeyValue)
这将返回一个由该特定EntityManager“管理”的Dog实例。每个EntityManager保证返回sam