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

Arj*_*jms 33 java hibernate jpa exception

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

@Entity
public class Foo {

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

当我坚持使用新的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
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

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

axt*_*avt 21

您可以使用merge()而不是persist()在这种情况下:

foo = entityManager.merge(foo); 
Run Code Online (Sandbox Code Playgroud)

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


Jim*_*ugh 7

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

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

编辑:

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

  • 它对所有JPA实现都不是更有效,可能只是你正在使用的那个.其他(例如DataNucleus)通过两种方式提供相同的效率 (2认同)