java - example - La cascada de JPA persiste y las referencias a entidades separadas arrojan PersistentObjectException. ¿Por qué?
jpa hibernate example (2)
Tengo una entidad Foo que hace referencia a una entidad Bar:
@Entity
public class Foo {
@OneToOne(cascade = {PERSIST, MERGE, REFRESH}, fetch = EAGER)
public Bar getBar() {
return bar;
}
}
Cuando insisto en un nuevo Foo, puede obtener una referencia a una nueva Barra o una Barra existente. Cuando recibe una barra existente, que está desacoplada, mi proveedor de JPA (Hibernate) arroja la siguiente excepción:
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
Cuando me aseguro de que la referencia a Bar se gestiona (adjunta) o cuando omito la cascada PERSIST en la relación, todo funciona bien.
Ninguna solución, sin embargo, es 100% satisfactoria. Si elimino la cascada, obviamente no puedo persistir un Foo con una referencia a un nuevo Bar más. Hacer la referencia a Bar managed necesita un código como este antes de persistir:
if (foo.getBar().getID() != null && !entityManager.contains(foo.getBar())) {
foo.setBar(entityManager.merge(foo.getUBar()));
}
entityManager.persist(foo);
Para una sola barra esto puede no parecer un gran problema, pero si tengo que tomar todas las propiedades en cuenta de esta manera, terminaré con un código bastante horrible que parece frustrar la razón de usar ORM en primer lugar. También podría bien persistir mi gráfico de objetos manualmente usando JDBC nuevamente.
Cuando se le presente una referencia de Barra existente, lo único que tiene que hacer JPA es tomar su ID e insertarla en una columna de la tabla que contiene Foo. Hace exactamente esto cuando Bar está conectado, pero arroja la excepción cuando se separa la barra.
Mi pregunta es; ¿Por qué necesita que se adjunte Bar? Seguramente su ID no cambiará cuando la instancia de Bar pase del estado separado al adjunto, y ese ID parece ser lo único que se necesita aquí.
¿Es esto quizás un error en Hibernate o me estoy perdiendo algo?
Puede usar merge()
lugar de persist()
en este caso:
foo = entityManager.merge(foo);
Cuando se aplica a la nueva instancia, merge()
hace persistente (en realidad, devuelve la instancia persistente con el mismo estado) y combina las referencias en cascada, como intenta hacer manualmente.
Si entiendo correctamente, solo necesita la referencia de Bar
para permitir que el nuevo Foo
tenga el valor de la clave externa (a la Bar
existente) cuando persista. Hay un método JPA en EntityManager
llamado getReference()
que podría serle útil en este caso. El método getReference()
es similar a find()
excepto que no se molestará en devolver una instancia administrada (de Bar
) a menos que ya esté almacenada en caché en el contexto de persistencia. Devolverá un objeto proxy que satisfará sus necesidades de clave externa para persistir en el objeto Foo
. No estoy seguro de si este es el tipo de solución que esperabas, pero pruébalo y mira si esto funciona para ti.
También noté en su código que está usando el acceso de estilo de "propiedad" en lugar del acceso de estilo de "campo" anotando su método getter (para la relación Bar
). ¿Alguna razón para eso? Se recomienda que anote los miembros en lugar de los getters por motivos de rendimiento. Se supone que es más eficiente para el proveedor de JPA acceder al campo directamente en lugar de a través de getters y setters.
EDITAR:
Como mencionó otra persona, el uso de una combinación en cascada () persistirá en nuevas entidades, así como fusionará entidades modificadas y volverá a conectar entidades independientes que tengan una relación con la opción de cascada MERGE. El uso de la opción de cascada PERSIST no volverá a conectar nada ni combinará nada y está destinado a ser utilizado cuando ese sea el comportamiento que desea.