ChatGPT解决这个技术问题 Extra ChatGPT

PersistentObjectException:分离的实体传递给 JPA 和 Hibernate 抛出的持久化

我有一个包含多对一关系的 JPA 持久对象模型:一个 Account 有多个 TransactionsTransaction 有一个 Account

这是代码片段:

@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 at 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,但是有两个用户使用同一个帐户,其中一个保留了一个实体(正确),当第二个尝试保留同一个实体时发生错误,那已经坚持。

D
Damiano

解决方案很简单,只需使用 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,则无法避免插入持久对象。所以这是唯一的解决方案。再次感谢你。
K
Kalle Richter

这是一个典型的双向一致性问题。它在 this linkthis link. 中得到了很好的讨论

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

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

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


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

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

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

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

就这样!

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

但只能将临时(新)实体传递给 persist(在本例中为 Transaction)。分离的(或其他非瞬态状态)可能不是(在这种情况下为 Account,因为它已经在 DB 中)。

因此,您会得到异常“传递给持久化的分离实体”Account 实体是指!不是您调用 persistTransaction

您通常不希望从孩子传播到父母。不幸的是,书籍(甚至是好书)和网络中有很多代码示例,它们正是这样做的。我不知道,为什么......也许有时只是一遍又一遍地复制而不加思考......

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


只是想补充一下,如果您真的打算将孩子与父母一起保存,并且还与孩子一起删除父母,例如人(父母)和地址(孩子)以及由DB自动生成的addressId,那么在对Person调用保存之前,只需制作在您的交易方法中调用保存地址。这样,它将与 DB 生成的 id 一起保存。对性能没有影响,因为 Hibenate 仍然进行 2 次查询,我们只是改变了查询的顺序。
如果我们不能传递任何东西,那么所有情况下都需要什么默认值。
很棒的解释@Eugen Labun。
A
Angad Bansode

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


好建议!但只有在生成 id 时。如果已分配,则设置 id 是正常的。
这对我有用。此外,您可以使用 TestEntityManager.persistAndFlush() 使实例受管理和持久化,然后将持久性上下文同步到底层数据库。返回原始源实体
这就像魅力一样
V
Vlad Mihalcea

删除子关联级联

因此,您需要从 @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 -> t.setAccount(this))
@FaizanAhmad 当您添加孩子而不将其添加到父母时,这不会涵盖这种情况。
Z
Zds

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

你没有在这里显示完整的代码,所以我不能仔细检查你的交易模式。解决这种情况的一种方法是,如果您在执行合并和持久化时没有活动的事务。在这种情况下,持久性提供程序应该为您执行的每个 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)。我仍然遇到同样的问题,
我不能说我完全理解为什么事情会这样工作,但是将持久化方法和视图到实体映射一起放在事务注释中解决了我的问题,非常感谢。
这实际上是一个比最受欢迎的解决方案更好的解决方案。
我们将管理我们的持久性上下文以使我们的实体保持在托管状态,而不是改变实体本身。
d
dan

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


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

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

实体

@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)的值


这是对我有帮助的答案,非常感谢!!
J
JJ Zabkar

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

为什么有效

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

参考:

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


谢谢!就我而言,我必须将我的 @Transactional 方法拆分为两个单独的 @Transactional 方法才能正常工作。
F
FazoM

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

Account -> 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 方法中的内容。


N
NemesisX00

在您的实体定义中,您没有为连接到 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")
d
dan

即使您的注释被正确声明以正确管理一对多关系,您仍然可能会遇到这个精确的异常。将新的子对象 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 值,因为它已经被管理。


Y
Yaocl

也许这是 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;
            }

s
stakahop

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

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.

J
James Siva
cascadeType.MERGE,fetch= FetchType.LAZY

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

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


S
Shahid Hussain Abbasi

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

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


这是试图避免的,至少我想保存子实体只在父对象中执行 .save
J
John Doe

这是我的修复。

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

@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 设置行。


H
H.Ostwal

在我的情况下,当使用持久方法时,我正在提交事务。在更改持久保存方法时,它得到了解决。


S
Shubham

如果上述解决方案不起作用,只需注释一次实体类的 getter 和 setter 方法,并且不要设置 id.(Primary key) 的值,那么这将起作用。


t
tomaytotomato

我遇到此问题的另一个原因是在事务中具有未由 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;

}

M
MrSolarius

此错误来自 JPA 生命周期。要解决,无需使用特定的装饰器。只需像这样使用合并加入实体:

entityManager.merge(transaction);

并且不要忘记正确设置您的 getter 和 setter,以便双方同步。