代码之家  ›  专栏  ›  技术社区  ›  Arjan Tijms Mike Van

JPA cascade持久化,对分离实体的引用抛出PersistentObjectException。为什么?

  •  33
  • Arjan Tijms Mike Van  · 技术社区  · 15 年前

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

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

    当我坚持一个新的FoO,它可以得到一个新的酒吧或现有酒吧的引用。当它得到一个已经分离的现有的条时,我的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的引用被管理(附加)时,或者当我忽略关系中的cascade persistent时,所有的工作都很好。

    然而,这两种解决方案都不是100%令人满意的。如果删除cascade persist,显然就不能再将Foo与一个新的条引用一起持久化了。在持久化之前,使对Bar managed的引用需要这样的代码:

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

    对于一个单条来说,这看起来并不是什么大事,但是如果我必须像这样考虑所有的属性,那么我最终会得到非常糟糕的代码,这些代码似乎一开始就击败了使用ORM的原因。我不妨再次使用JDBC手动持久化对象图。

    当给定一个现有的条引用时,JPA唯一需要做的就是获取它的ID,并将它插入到包含FO的表的列中。它在附加Bar时确实执行此操作,但在分离Bar时抛出异常。

    我的问题是,为什么它需要附加栏?当然,当Bar实例从detached状态转换到attached状态时,它的ID不会改变,而且这个ID似乎是这里唯一需要的东西。

    这可能是冬眠中的虫子,还是我遗漏了什么?

    2 回复  |  直到 15 年前
        1
  •  21
  •   axtavt    14 年前

    你可以用 merge() 而不是 persist()

    foo = entityManager.merge(foo); 
    

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

        2
  •  7
  •   Jim Tough    15 年前

    如果我理解正确,你只需要 Bar 允许新的 Foo 具有外键值(对现有的) 酒吧 EntityManager 打电话 getReference() 这可能对你的案子有用。这个 获取引用() 方法类似于 find() 只不过它不会费心返回(的)托管实例 酒吧 )除非它碰巧已经被缓存在持久性上下文中。它将返回一个代理对象,该对象将满足您的外键需要,以便将 反对。我不确定这是否是你所希望的解决方案,但试试看这是否适合你。

    我还注意到,通过注释getter方法(对于 酒吧 关系)。有什么理由吗?出于性能原因,建议您注释成员而不是getter。JPA提供者应该更有效地直接访问字段,而不是通过getter和setter。

    编辑:

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