2010-11-28 18 views
32

que tienen una entidad Foo que hace referencia a una barra de entidad:La cascada de JPA persiste y las referencias a entidades separadas arrojan PersistentObjectException. ¿Por qué?

@Entity 
public class Foo { 

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

Cuando persisto un nuevo Foo, se puede obtener una referencia a un nuevo bar o un bar existente. Cuando se pone una barra existente, que pasa a ser individual, el profesional de la APP (Hibernate) lanza 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 sea asegúrese de que la referencia a la barra es administrado (adjunto) 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. Haciendo referencia a la barra logró requiere un código como éste antes de la persistencia:

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

Para una sola barra que esto podría no parecer una gran cosa, pero si tengo que tomar todas las propiedades en cuenta como esto voy a 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 falta algo?

+2

Puede eliminar PERSIST de la cascada, y verificar si (id == null) {em.persiste (foo.getBar())} Al menos hace su if simplier. – amorfis

+0

Cierto, eso de hecho lo haría un poco más simple. Gracias por la sugerencia. –

+0

Por supuesto, incluso con sentencias if ligeramente más simples, el código persistente aún tendría que recorrer todo el gráfico del objeto, lo cual es algo que intento desesperadamente evitar. –

Respuesta

21

Puede utilizar merge() en lugar de persist() en este caso:

foo = entityManager.merge(foo); 

Cuando se aplica a la nueva instancia, merge() hace que sea persistente (en realidad - devuelve la instancia persistente con el mismo estado), y se funde en cascada referencias , como intentas hacer manualmente

+0

De hecho, eso parece ser el truco. merge() funciona básicamente como las operaciones tradicionales de persistencia y actualización, parece. Sin embargo, creo que el javadoc podría haber sido un poco más claro sobre este tema. También todavía no entiendo completamente por qué persist() insiste en la referencia que se está administrando aunque parece que no hay razón para ello, pero prácticamente fusionar() parece ser una muy buena alternativa. –

+0

¿Es posible hacer la "fusión" no por la identificación, sino por otras condiciones? Obtengo objetos de una fuente externa y si coinciden el nombre y la fecha de cumpleaños de la calle, quiero recuperar ese objeto. Pero el objeto es más profundo en un árbol persistente en cascada, por lo que no haré una carga desde la base de datos manualmente. – flaschenpost

+0

¿De qué sirve tener un método de "fusión" Y un método de "persistencia"? En este caso, tenemos que usar un método de "combinación" para crear un objeto, lo cual es bastante extraño. ¿O me estoy perdiendo algo? – Mat

7

Si entiendo correctamente, solo necesita la referencia Bar para permitir que el nuevo Foo tenga el valor de la clave externa (al Bar existente) cuando persiste. Hay un método JPA en el 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 mantener 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é por su código que está utilizando 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.

EDIT:

Como alguien ha mencionado, utilizando una intercalación en cascada() persistirán nuevas entidades, así como las entidades modificada de combinación y vuelva a colocar ENTIDADES separadas que tienen una relación con la opción de intercalación en cascada. 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.

+0

Tienes razón. La referencia de barras, IFF es una entidad existente, solo es necesaria para permitir que el nuevo Foo tenga el FK en Bar. El getReference() parece una alternativa mucho más barata para find o merge() en este caso. El problema, sin embargo, sigue siendo que no me gusta caminar manualmente el gráfico del objeto y reemplazar todas las referencias separadas por las adjuntas. También todavía no entiendo por qué JPA insiste en tener una referencia administrada o por qué no hay un recursivo "adjuntar todo" en una raíz de agregación. –

+0

** Se supone que es más eficiente para el proveedor de JPA acceder al campo directamente que a través de getters y setters. ** No había oído hablar de eso antes, ¡pero gracias por la sugerencia! –

+2

No es más eficiente para todas las implementaciones de JPA, quizás solo la que estás usando. Otros (por ejemplo, DataNucleus) proporcionan la misma eficacia en ambos sentidos – DataNucleus

Cuestiones relacionadas