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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4294671/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 15:28:41  来源:igfitidea点击:

JPA cascade persist and references to detached entities throws PersistentObjectException. Why?

javahibernateexceptionjpa

提问by Arjan Tijms

I have an entity Foo that references an entity Bar:

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

@Entity
public class Foo {

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

When I persist a new Foo, it can get a reference to either a new Bar or an existing Bar. When it gets an existing Bar, which happens to be detached, my JPA provider (Hibernate) throws the following exception:

当我保留一个新的 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.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

When I either make sure the reference to Bar is managed (attached) or when I omit the cascade PERSIST in the relation, all works well.

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

Neither solution however is 100% satisfactory. If I remove the cascade persist, I obviously can't persist a Foo with a reference to a new Bar anymore. Making the reference to Bar managed necessitates code like this prior to persisting:

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

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

For a single Bar this might not seem like a big deal, but if I have to take all properties into account like this I'll end up with pretty horrible code that seems to defeat the reason of using ORM in the first place. I might as well well persist my object graph manually using JDBC again.

对于单个 Bar 来说,这似乎没什么大不了的,但是如果我必须像这样考虑所有属性,我最终会得到非常糟糕的代码,这似乎一开始就打败了使用 ORM 的原因。我还不如再次使用 JDBC 手动保留我的对象图。

When given an existing Bar reference the only thing JPA has to do is take its ID and insert that in a column of the table that holds Foo. It does exactly this when Bar is attached, but throws the exception when Bar is detached.

当给定一个现有的 Bar 引用时,JPA 唯一要做的就是获取它的 ID 并将它插入到包含 Foo 的表的列中。当 Bar 被连接时它会这样做,但是当 Bar 被分离时会抛出异常。

My question is; why does it need Bar to be attached? Surely its ID won't change when the Bar instance transitions from detached to attached state, and that ID seems to be the only thing needed here.

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

Is this perhaps a bug in Hibernate or am I missing something?

这可能是 Hibernate 中的错误还是我遗漏了什么?

采纳答案by axtavt

You can use merge()instead of persist()in this case:

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

foo = entityManager.merge(foo); 

When applied to the new instance, merge()makes it persistent (actually - returns the persistent instance with the same state), and merges cascaded references, as you try to do manually.

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

回答by Jim Tough

If I understand correctly, you just need the Barreference to allow the new Footo have the foreign key value (to the existing Bar) when persisting. There is a JPA method on the EntityManagercalled getReference()that might be useful to you for this case. The getReference()method is similar to find()except that it won't bother to return a managed instance (of Bar) unless it happens to already be cached in the persistence context. It will return a proxy object that will satisfy your foreign key needs in order to persist the Fooobject. I'm not sure if this is the kind of solution you were hoping for, but give it a try and see if this works for you.

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

I also noticed from your code that you're using "property" style access instead of "field" style access by annotating your getter method (for the Barrelationship). Any reason for that? It's recommended that you annotate the members rather than the getters for performance reasons. It is supposed to be more efficient for the JPA provider to access the field directly rather than via getters and setters.

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

EDIT:

编辑:

As someone else mentioned, using a cascade merge() will persist new entities as well as merge modified entities and reattach detached entites that have a relationship with the MERGE cascade option. Using PERSIST cascade option won't reattach anything or merge anything and is meant to be used when that is the behavior you want.

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