【问题标题】:PersistentObjectException: detached entity passed to persist thrown by JPA and HibernatePersistentObjectException:分离的实体传递给 JPA 和 Hibernate 抛出的持久化
【发布时间】:2012-11-02 10:35:35
【问题描述】:

我有一个包含多对一关系的 JPA 持久对象模型:Account 有许多 Transactions。一个Transaction 有一个Account

这是代码的sn-p:

@Entity
public class Transaction {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
    private Account fromAccount;
....

@Entity
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
    private Set<Transaction> transactions;

我能够创建一个Account 对象,向其中添加事务,并正确地持久化Account 对象。但是,当我创建一个事务时,使用现有的已持久化帐户,并持久化事务,我得到一个异常:

原因:org.hibernate.PersistentObjectException:分离的实体传递给持久化:com.paulsanwald.Account 在 org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:141)

因此,我能够持久化包含事务的Account,但不能持久化具有Account 的事务。我认为这是因为Account 可能没有附加,但是这段代码仍然给了我同样的异常:

if (account.getId()!=null) {
    account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
 // the below fails with a "detached entity" message. why?
entityManager.persist(transaction);

如何正确保存与已持久化的Account 对象关联的Transaction

【问题讨论】:

  • 在我的例子中,我正在设置一个我试图使用实体管理器持久化的实体的 ID。当我删除了 id 的设置器时,它开始正常工作。
  • 在我的情况下,我没有设置 id,但是有两个用户使用同一个帐户,其中一个用户(正确地)保留了一个实体,而当第二个用户尝试时发生错误持久化已经持久化的同一个实体。

标签: java hibernate jpa entity persist


【解决方案1】:

解决方案很简单,只需使用CascadeType.MERGE 代替CascadeType.PERSISTCascadeType.ALL

我遇到了同样的问题,CascadeType.MERGE 为我工作。

我希望你被排序。

【讨论】:

  • 令人惊讶的是,它也为我工作。这是没有意义的,因为 CascadeType.ALL 包括所有其他级联类型...... WTF?我有 spring 4.0.4、spring data jpa 1.8.0 和 hibernate 4.X .. 有没有人有任何想法为什么 ALL 不起作用,但 MERGE 起作用?
  • @VadimKirilchuk 这对我也有用,而且完全有道理。由于 Transaction 是 PERSISTED,它也会尝试 PERSIST Account 并且由于 Account 已经在数据库中,所以这不起作用。但是使用 CascadeType.MERGE 会自动合并帐户。
  • 如果您不使用事务,可能会发生这种情况。
  • 另一种解决方案:尽量不要插入已经持久化的对象:)
  • 谢谢你。如果您限制引用键为 NOT NULL,则无法避免插入持久对象。所以这是唯一的解决方案。再次感谢您。
【解决方案2】:

这是一个典型的双向一致性问题。 this linkthis link. 对此进行了很好的讨论

根据前面 2 个链接中的文章,您需要在双向关系的两侧修复您的设置器。一侧的示例设置器位于this link.

Many 方面的示例设置器位于 this link.

更正设置器后,您希望将实体访问类型声明为“属性”。声明“属性”访问类型的最佳实践是将所有注释从成员属性移动到相应的 getter。一个重要的词是不要在实体类中混合“字段”和“属性”访问类型,否则 JSR-317 规范未定义行为。

【讨论】:

  • ps:@Id注解是hibernate用来识别访问类型的注解。
  • 例外情况是:detached entity passed to persist 为什么提高一致性可以使它起作用?好的,一致性已修复,但对象仍然分离。
  • @Sam,非常感谢您的解释。但是,我还是不明白。我看到如果没有“特殊”设置器,则无法满足双向关系。但是,我不明白为什么对象被分离。
  • 请不要发布仅通过链接回答的答案。
  • 看不出这个答案与问题有什么关系?
【解决方案3】:

从子实体Transaction中移除级联,应该只是:

@Entity class Transaction {
    @ManyToOne // no cascading here!
    private Account account;
}

FetchType.EAGER 可以删除,它是@ManyToOne 的默认值)

就是这样!

为什么?通过在子实体Transaction 上说“全部级联”,您需要将每个数据库操作传播到父实体Account。如果你随后执行persist(transaction)persist(account) 也会被调用。

但只有临时(新)实体可以传递给persist(在这种情况下为Transaction)。分离的(或其他非瞬态状态)可能不会(在这种情况下为Account,因为它已经在数据库中)。

因此,您会得到异常“传递给持久化的分离实体”Account 实体的意思是!不是你打电话给persistTransaction


您通常不希望从孩子传播到父母。不幸的是,书籍(即使是好的)和网络中有很多代码示例,它们正是这样做的。我不知道,为什么……也许有时候只是一遍又一遍地抄袭……

猜猜如果你打电话给remove(transaction) 在@ManyToOne 中仍然有“cascade ALL”会发生什么? account(顺便说一句,还有所有其他事务!)也将从数据库中删除。但这不是你的本意,是吗?

【讨论】:

  • 只想添加,如果您的意图是真的与父母一起保存孩子,并与孩子一起删除父母,例如人(父母)和地址(孩子)以及由数据库自动生成的地址ID然后在对 Person 调用 save 之前,只需在您的事务方法中调用 save on address 即可。这样,它将与 DB 生成的 id 一起保存。对性能没有影响,因为 Hibenate 仍然进行 2 次查询,我们只是改变了查询的顺序。
  • 如果我们不能传递任何东西,那么所有情况下的默认值是多少。
  • 很棒的解释@Eugen Labun。
【解决方案4】:

不要将 id(pk) 传递给 persist 方法或尝试使用 save() 方法而不是 persist()。

【讨论】:

  • 好建议!但只有在生成 id 时。如果已分配,则设置 id 是正常的。
  • 这对我有用。此外,您可以使用TestEntityManager.persistAndFlush() 来管理和持久化实例,然后将持久化上下文同步到底层数据库。返回原始源实体
  • 这很有魅力
【解决方案5】:

移除子关联级联

因此,您需要从 @ManyToOne 关联中删除 @CascadeType.ALL。子实体不应级联到父关联。只有父实体应该级联到子实体。

@ManyToOne(fetch= FetchType.LAZY)

请注意,我将fetch 属性设置为FetchType.LAZY,因为急切获取对性能非常不利。

设置关联的双方

只要有双向关联,就需要在父实体中使用addChildremoveChild 方法同步双方:

public void addTransaction(Transaction transaction) {
    transcations.add(transaction);
    transaction.setAccount(this);
}

public void removeTransaction(Transaction transaction) {
    transcations.remove(transaction);
    transaction.setAccount(null);
}

【讨论】:

  • 而不是管理,如果我们添加带有@Prepersist 的方法并在该方法中,只需在所有子实体中设置this 引用呢? void prePersist(){ transactions.foreach( t -&gt; t.setAccount(this))
  • @FaizanAhmad 这不会涵盖当您添加子项而不将其添加到父项时的情况。
【解决方案6】:

使用合并既冒险又棘手,因此在您的情况下这是一个肮脏的解决方法。您至少需要记住,当您将实体对象传递给合并时,它停止附加到事务,而是返回一个新的、现在附加的实体。这意味着,如果任何人仍然拥有旧实体对象,则对其所做的更改将被静默忽略并在提交时丢弃。

您没有在此处显示完整的代码,因此我无法仔细检查您的交易模式。解决这种情况的一种方法是,如果您在执行合并和持久化时没有活动的事务。在这种情况下,持久性提供程序应该为您执行的每个 JPA 操作打开一个新事务,并在调用返回之前立即提交和关闭它。如果是这种情况,合并将在第一个事务中运行,然后在合并方法返回后,事务完成并关闭,返回的实体现在被分离。然后,它下面的持久化将打开第二个事务,并尝试引用一个已分离的实体,从而给出异常。除非您非常清楚自己在做什么,否则请始终将您的代码包装在事务中。

使用容器管理的事务,它看起来像这样。请注意:这假定该方法位于会话 bean 中并通过本地或远程接口调用。

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void storeAccount(Account account) {
    ...

    if (account.getId()!=null) {
        account = entityManager.merge(account);
    }

    Transaction transaction = new Transaction(account,"other stuff");

    entityManager.persist(account);
}

【讨论】:

  • 我面临同样的问题。我在服务层使用了@Transaction(readonly=false)。我仍然遇到同样的问题,
  • 我不能说我完全理解为什么事情会这样工作,但是将持久方法和视图到实体映射一起放在事务注释中解决了我的问题,非常感谢。
  • 这实际上是一个比最受欢迎的解决方案更好的解决方案。
【解决方案7】:

可能在这种情况下,您使用合并逻辑获得了您的account 对象,而persist 用于持久化新对象,如果层次结构已经具有持久化对象,它会报错。在这种情况下,您应该使用saveOrUpdate,而不是persist

【讨论】:

  • 它是 JPA,所以我认为类似的方法是 .merge(),但这给了我同样的例外。需要明确的是,Transaction 是一个新对象,Account 不是。
  • @PaulSanwald 在transaction 对象上使用merge 你会得到同样的错误吗?
  • 实际上,不,我说错了。如果我 .merge(transaction),则事务根本不会持久化。
  • @PaulSanwald 嗯,你确定 transaction 没有被持久化吗?你是怎么查的。请注意,merge 正在返回对持久对象的引用。
  • .merge() 返回的对象的 id 为空。另外,之后我正在执行 .findAll(),但我的对象不存在。
【解决方案8】:

我的 Spring Data 基于 JPA 的答案:我只是在我的外部方法中添加了一个 @Transactional 注释。

为什么会起作用

子实体立即分离,因为没有活动的 Hibernate 会话上下文。提供 Spring(Data JPA)事务可确保 Hibernate Session 存在。

参考:

https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/

【讨论】:

  • 谢谢!就我而言,我必须将我的 @Transactional 方法拆分为两个单独的 @Transactional 方法才能正常工作。
【解决方案9】:

在您的实体定义中,您没有为连接到TransactionAccount 指定@JoinColumn。你会想要这样的东西:

@Entity
public class Transaction {
    @ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
    @JoinColumn(name = "accountId", referencedColumnName = "id")
    private Account fromAccount;
}

编辑:好吧,我想如果你在课堂上使用@Table 注释会很有用。呵呵。 :)

【讨论】:

  • 是的,我不认为是这样,但我添加了@JoinColumn(name = "fromAccount_id", referencedColumnName = "id"),但它不起作用:)。跨度>
  • 是的,我通常不使用映射 xml 文件将实体映射到表,所以我通常假设它是基于注释的。但如果我不得不猜测,您使用的是 hibernate.xml 将实体映射到表,对吧?
  • 不,我使用的是 Spring Data JPA,所以它都是基于注释的。我在另一边有一个“mappedBy”注释:@OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
【解决方案10】:

如果没有任何帮助并且您仍然遇到此异常,请查看您的 equals() 方法 - 并且不要在其中包含子集合。尤其是如果您有深层结构的嵌入式集合(例如 A 包含 Bs,B 包含 Cs 等)。

Account -&gt; Transactions为例:

  public class Account {

    private Long id;
    private String accountName;
    private Set<Transaction> transactions;

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (!(obj instanceof Account))
        return false;
      Account other = (Account) obj;
      return Objects.equals(this.id, other.id)
          && Objects.equals(this.accountName, other.accountName)
          && Objects.equals(this.transactions, other.transactions); // <--- REMOVE THIS!
    }
  }

在上面的示例中,从equals() 检查中删除事务。这是因为休眠意味着您不会尝试更新旧对象,而是将新对象传递给持久化,每当您更改子集合上的元素时。
当然,这种解决方案并不适合所有应用程序,您应该仔细设计要包含在 equalshashCode 方法中的内容。

【讨论】:

    【解决方案11】:

    一个老问题,但最近遇到了同样的问题。在这里分享我的经验。

    实体

    @Data
    @Entity
    @Table(name = "COURSE")
    public class Course  {
    
        @Id
        @GeneratedValue
        private Long id;
    }
    

    保存实体(JUnit)

    Course course = new Course(10L, "testcourse", "DummyCourse");
    testEntityManager.persist(course);
    

    修复

    Course course = new Course(null, "testcourse", "DummyCourse");
    testEntityManager.persist(course);
    

    结论:如果实体类的主键(id)有@GeneratedValue,那么确保你没有传递主键(id)的值

    【讨论】:

    • 这是对我有帮助的答案,非常感谢!!
    【解决方案12】:

    即使您的注释被正确声明以正确管理一对多关系,您仍然可能会遇到这个精确的异常。将新的子对象 Transaction 添加到附加的数据模型时,您需要管理主键值 - 除非您不应该这样做。如果您在调用persist(T) 之前为声明如下的子实体提供主键值,则会遇到此异常。

    @Entity
    public class Transaction {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
    ....
    

    在这种情况下,注释声明数据库将在插入时管理实体主键值的生成。自己提供一个(例如通过 Id 的设置器)会导致此异常。

    另外,但实际上相同,此注释声明导致相同的异常:

    @Entity
    public class Transaction {
        @Id
        @org.hibernate.annotations.GenericGenerator(name="system-uuid", strategy="uuid")
        @GeneratedValue(generator="system-uuid")
        private Long id;
    ....
    

    因此,不要在您的应用程序代码中设置 id 值,因为它已经被管理了。

    【讨论】:

      【解决方案13】:

      可能是 OpenJPA 的错误,回滚时会重置 @Version 字段,但 pcVersionInit 保持为真。我有一个 AbstraceEntity 声明了 @Version 字段。我可以通过重置 pcVersionInit 字段来解决它。但这不是一个好主意。我认为当有级联持久实体时它不起作用。

          private static Field PC_VERSION_INIT = null;
          static {
              try {
                  PC_VERSION_INIT = AbstractEntity.class.getDeclaredField("pcVersionInit");
                  PC_VERSION_INIT.setAccessible(true);
              } catch (NoSuchFieldException | SecurityException e) {
              }
          }
      
          public T call(final EntityManager em) {
                      if (PC_VERSION_INIT != null && isDetached(entity)) {
                          try {
                              PC_VERSION_INIT.set(entity, false);
                          } catch (IllegalArgumentException | IllegalAccessException e) {
                          }
                      }
                      em.persist(entity);
                      return entity;
                  }
      
                  /**
                   * @param entity
                   * @param detached
                   * @return
                   */
                  private boolean isDetached(final Object entity) {
                      if (entity instanceof PersistenceCapable) {
                          PersistenceCapable pc = (PersistenceCapable) entity;
                          if (pc.pcIsDetached() == Boolean.TRUE) {
                              return true;
                          }
                      }
                      return false;
                  }
      

      【讨论】:

        【解决方案14】:

        您需要为每个账户设置交易。

        foreach(Account account : accounts){
            account.setTransaction(transactionObj);
        }
        

        或者在很多方面将 ids 设置为 null 就足够了(如果合适的话)。

        // list of existing accounts
        List<Account> accounts = new ArrayList<>(transactionObj.getAccounts());
        
        foreach(Account account : accounts){
            account.setId(null);
        }
        
        transactionObj.setAccounts(accounts);
        
        // just persist transactionObj using EntityManager merge() method.
        

        【讨论】:

          【解决方案15】:
          cascadeType.MERGE,fetch= FetchType.LAZY
          

          【讨论】:

          • 嗨,詹姆斯,欢迎您,您应该尽量避免仅使用代码的答案。请说明这如何解决问题中所述的问题(以及何时适用,API 级别等)。
          • VLQ 审阅者:见meta.stackoverflow.com/questions/260411/…。仅代码的答案不值得删除,适当的操作是选择“看起来不错”。
          【解决方案16】:

          通过在下一个之前保存依赖对象来解决。

          这发生在我身上,因为我没有设置 Id(不是自动生成的)。并尝试与@ManytoOne 关系保存

          【讨论】:

          • 这是试图避免的,至少我想保存子实体只在父对象中执行 .save
          【解决方案17】:

          在我的情况下,当使用 persist 方法时,我正在提交事务。 在将 persist 更改为 save 方法后,它得到了解决。

          【讨论】:

            【解决方案18】:

            如果上述解决方案不起作用,只需注释一次实体类的getter和setter方法,不要设置id的值。(主键) 这样就可以了。

            【讨论】:

              【解决方案19】:

              @OneToMany(mappedBy = "xxxx", cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE}) 为我工作。

              【讨论】:

                【解决方案20】:

                这是我的解决方法。

                下面是我的实体。标记 id 用 @GeneratedValue(strategy = GenerationType.AUTO) 注释,这意味着 id 将由 Hibernate 生成。 实体对象创建时不要设置因为它将由 Hibernate 自动生成。 请注意,如果实体 id 字段没有用@GeneratedValue 标记,那么不手动为 id 分配值也是一种犯罪,这将受到 IdentifierGenerationException: 在调用 save()

                @Entity
                @Data
                @NamedQuery(name = "SimpleObject.findAll", query="Select s FROM SimpleObject s")
                public class SimpleObject {
                
                    @Id
                    @GeneratedValue(strategy = GenerationType.AUTO)
                    private Long id;
                
                    @Column
                    private String key;
                
                    @Column
                    private String value;
                
                }
                

                这是我的主要课程。

                public class SimpleObjectMain {
                
                    public static void main(String[] args) {
                
                        System.out.println("Hello Hello From SimpleObjectMain");
                
                        SimpleObject simpleObject = new SimpleObject();
                        simpleObject.setId(420L); // Not right, when id is a generated value then no need to set this.
                        simpleObject.setKey("Friend");
                        simpleObject.setValue("Bani");
                
                        EntityManager entityManager = EntityManagerUtil.getEntityManager();
                        entityManager.getTransaction().begin();
                        entityManager.persist(simpleObject);
                        entityManager.getTransaction().commit();
                
                        List<SimpleObject> simpleObjectList = entityManager.createNamedQuery("SimpleObject.findAll").getResultList();
                        for(SimpleObject simple : simpleObjectList){
                            System.out.println(simple);
                        }
                
                        entityManager.close();
                        
                    }
                }
                

                当我尝试保存它时,它正在扔掉它

                PersistentObjectException: detached entity passed to persist.
                

                我需要解决的只是删除 main 方法中 simpleObject 的 id 设置行。

                【讨论】:

                  【解决方案21】:

                  我遇到此问题的另一个原因是事务中存在未由 Hibernate 版本化的实体。

                  为所有映射实体添加@Version注解

                  @Entity
                  public class Customer {
                  
                      @Id
                      @GeneratedValue(strategy = GenerationType.AUTO)
                      private UUID id;
                  
                      @Version
                      private Integer version;
                  
                      @OneToMany(cascade = CascadeType.ALL)
                      @JoinColumn(name = "orders")
                      private CustomerOrders orders;
                  
                  }
                  
                  @Entity
                  public class CustomerOrders {
                  
                      @Id
                      @GeneratedValue(strategy = GenerationType.AUTO)
                      private UUID id;
                  
                      @Version
                      private Integer version;
                  
                      private BigDecimal value;
                  
                  }
                  

                  【讨论】:

                    猜你喜欢
                    • 2015-12-26
                    • 1970-01-01
                    • 2017-10-21
                    • 1970-01-01
                    • 2013-06-26
                    相关资源
                    最近更新 更多