JPA级联持久化并且对分离实体的引用会抛出PersistentObjectException。为什么?

时间:2010-11-28 00:27:02

标签: java hibernate exception jpa

我有一个引用实体Bar的实体Foo:

@Entity
public class Foo {

    @OneToOne(cascade = {PERSIST, MERGE, REFRESH}, fetch = EAGER)
    public Bar getBar() {
        return bar;
    }
}

当我坚持使用新的Foo时,它可以获得对新Bar或现有Bar的引用。当它获得一个恰好分离的现有Bar时,我的JPA提供程序(Hibernate)抛出以下异常:

Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.example.Bar
 at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:102)
 at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:636)
 at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:628)
 at org.hibernate.engine.EJB3CascadingAction$1.cascade(EJB3CascadingAction.java:28)
 at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:291)
 at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:239)
 at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:192)
 at org.hibernate.engine.Cascade.cascade(Cascade.java:153)
 at org.hibernate.event.def.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:454)
 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:130)
 at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:49)
 at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:154)
 at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:110)
 at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:61)
 at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:645)
 at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:619)
 at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:623)
 at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:220)
 ... 112 more

当我确保管理(附加)Bar的引用或者在关系中省略级联PERSIST时,一切都运行正常。

然而,这两种解决方案都不是100%令人满意。如果我删除级联持续存在,我显然不能再坚持引用新的Bar了。在持久化之前引用Bar管理需要这样的代码:

if (foo.getBar().getID() != null && !entityManager.contains(foo.getBar())) {
    foo.setBar(entityManager.merge(foo.getUBar()));
}
entityManager.persist(foo);

对于一个单独的Bar这可能看起来不是什么大问题,但如果我必须考虑所有这些属性,我会最终得到相当糟糕的代码,这似乎打败了首先使用ORM的原因。我也可以再次使用JDBC手动保存我的对象图。

当给定现有的Bar引用时,JPA唯一要做的就是获取它的ID并将其插入到包含Foo的表的列中。当附加Bar时它会完成此操作,但在分离Bar时抛出异常。

我的问题是;为什么要附加Bar?当Bar实例从分离状态转换为附加状态时,它的ID不会改变,并且该ID似乎是这里唯一需要的东西。

这可能是Hibernate中的一个错误,还是我错过了什么?

2 个答案:

答案 0 :(得分:21)

在这种情况下,您可以使用merge()代替persist()

foo = entityManager.merge(foo); 

当应用于新实例时,merge()使其持久化(实际上 - 返回具有相同状态的持久化实例),并在您尝试手动执行时合并级联引用。

答案 1 :(得分:7)

如果我理解正确,您只需要Bar引用以允许新Foo在保持时具有外键值(对于现有Bar)。名为EntityManager的{​​{1}}上有一个JPA方法,对于这种情况可能对您有用。 getReference()方法与getReference()类似,只是除非它恰好已经在持久化上下文中缓存,否则它将无需返回托管实例(find())。它将返回一个代理对象,该对象将满足您的外键需求,以便持久保存Bar对象。我不确定这是否是您希望的那种解决方案,但请试一试,看看这是否适合您。

我还从您的代码中注意到,您通过注释getter方法(对于Foo关系)使用“property”样式访问而不是“field”样式访问。有什么理由吗?出于性能原因,建议您注释成员而不是getter。 JPA提供者应该更有效地直接访问该字段,而不是通过getter和setter。

修改

正如其他人提到的,使用级联merge()将持久保存新实体以及合并已修改的实体并重新附加与MERGE级联选项有关系的分离的entites。使用PERSIST级联选项不会重新附加任何内容或合并任何内容,而是在您想要的行为时使用。