Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I have an entity Foo that references an entity Bar:

@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:

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 

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.

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:

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.

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.

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.

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

like image 821
Arjan Tijms Avatar asked Nov 28 '10 00:11

Arjan Tijms


People also ask

How do you resolve a detached entity passed to persist?

The solution is simple, just use the CascadeType. MERGE instead of CascadeType. PERSIST or CascadeType. ALL .

What is detached entity in JPA?

A detached entity (a.k.a. a detached object) is an object that has the same ID as an entity in the persistence store but that is no longer part of a persistence context (the scope of an EntityManager session).

What does cascade persist do?

Cascade Type PERSIST propagates the persist operation from a parent to a child entity. When we save the person entity, the address entity will also get saved.

What is the difference between persist and merge in JPA?

Persist should be called only on new entities, while merge is meant to reattach detached entities. If you're using the assigned generator, using merge instead of persist can cause a redundant SQL statement.


1 Answers

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

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.

like image 176
axtavt Avatar answered Sep 17 '22 15:09

axtavt