Tengo un modelo de objetos con JPA que contiene una relación de muchos a uno: un Account
tiene muchos Transactions
. A Transaction
tiene uno Account
.
Aquí hay un fragmento del código:
@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;
Puedo crear un Account
objeto, agregarle transacciones y mantener el Account
objeto correctamente. Pero, cuando creo una transacción, usando una cuenta ya existente y persistiendo la transacción , obtengo una excepción:
Causado por: org.hibernate.PersistentObjectException: entidad separada pasada a persistir: com.paulsanwald.Account at org.hibernate.event.internal.DefaultPersistEventListener.onPersist (DefaultPersistEventListener.java:141)
Entonces, puedo persistir un Account
que contiene transacciones, pero no una Transacción que tiene un Account
. Pensé que esto se debía a que Account
podría no estar adjunto, pero este código todavía me da la misma excepción:
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);
¿Cómo puedo guardar correctamente un Transaction
asociado con un Account
objeto ya persistente ?
Respuestas:
Este es un problema típico de consistencia bidireccional. Está bien discutido en este enlace , así como en este enlace.
De acuerdo con los artículos en los 2 enlaces anteriores, debe corregir sus establecedores en ambos lados de la relación bidireccional. Un setter de ejemplo para el Un lado está en este enlace.
Un ejemplo de establecimiento para el lado Muchos está en este enlace.
Después de corregir sus establecedores, desea declarar que el tipo de acceso de entidad es "Propiedad". La mejor práctica para declarar el tipo de acceso "Propiedad" es mover TODAS las anotaciones de las propiedades del miembro a los captadores correspondientes. Una gran advertencia es no mezclar los tipos de acceso "Campo" y "Propiedad" dentro de la clase de entidad; de lo contrario, el comportamiento no está definido por las especificaciones JSR-317.
fuente
detached entity passed to persist
¿Por qué mejorar la consistencia hace que funcione? Ok, se reparó la consistencia pero el objeto todavía está separado.La solución es simple, solo use el en
CascadeType.MERGE
lugar deCascadeType.PERSIST
oCascadeType.ALL
.He tenido el mismo problema y
CascadeType.MERGE
ha funcionado para mí.Espero que estés ordenado
fuente
Elimine la cascada de la entidad secundaria
Transaction
, debería ser solo:(
FetchType.EAGER
se puede quitar y es el valor predeterminado para@ManyToOne
)¡Eso es todo!
¿Por qué? Al decir "en cascada TODOS" en la entidad secundaria
Transaction
, necesita que cada operación de DB se propague a la entidad principalAccount
. Si lo hacespersist(transaction)
,persist(account)
se invocará también.Pero solo las entidades transitorias (nuevas) pueden pasarse a
persist
(Transaction
en este caso). Los desconectados (u otros estados no transitorios) pueden no hacerlo (Account
en este caso, ya que está en DB).Por lo tanto, obtiene la excepción "entidad separada pasada para persistir" . ¡La
Account
entidad está destinada! No elTransaction
que llamaspersist
.Por lo general, no desea propagarse de hijo a padre. Desafortunadamente, hay muchos ejemplos de código en los libros (incluso en los buenos) y a través de la red, que hacen exactamente eso. No sé por qué ... Quizás a veces simplemente se copia una y otra vez sin pensarlo mucho ...
¿Adivina qué sucede si llamas
remove(transaction)
aún teniendo "cascada TODOS" en ese @ManyToOne? Elaccount
(por cierto, con todas las demás transacciones!) También se eliminará de la base de datos. Pero esa no era tu intención, ¿verdad?fuente
Usar la fusión es arriesgado y complicado, por lo que es una solución sucia en su caso. Debe recordar al menos que cuando pasa un objeto de entidad para fusionar, deja de estar adjunto a la transacción y en su lugar se devuelve una nueva entidad ahora adjunta. Esto significa que si alguien tiene el antiguo objeto de entidad todavía en su posesión, los cambios en él se ignoran silenciosamente y se descartan en la confirmación.
No está mostrando el código completo aquí, por lo que no puedo verificar su patrón de transacción. Una forma de llegar a una situación como esta es si no tiene una transacción activa al ejecutar la fusión y persistir. En ese caso, se espera que el proveedor de persistencia abra una nueva transacción para cada operación JPA que realice e inmediatamente confirme y cierre antes de que vuelva la llamada. Si este es el caso, la fusión se ejecutará en una primera transacción y luego, una vez que regrese el método de fusión, la transacción se completa y se cierra y la entidad devuelta ahora se separa. La persistencia a continuación abriría una segunda transacción y trataría de referirse a una entidad separada, dando una excepción. Envuelva siempre su código dentro de una transacción a menos que sepa muy bien lo que está haciendo.
El uso de transacciones gestionadas por contenedor se vería así. Tenga en cuenta: esto supone que el método está dentro de un bean de sesión y se llama a través de la interfaz local o remota.
fuente
@Transaction(readonly=false)
servicio en la capa de servicio. Todavía tengo el mismo problema.Probablemente en este caso haya obtenido su
account
objeto utilizando la lógica de fusión, ypersist
se usa para persistir objetos nuevos y se quejará si la jerarquía tiene un objeto ya persistente. Debe usarsaveOrUpdate
en tales casos, en lugar depersist
.fuente
merge
eltransaction
objeto obtienes el mismo error?transaction
no persistió? ¿Cómo lo comprobaste? Tenga en cuenta quemerge
está devolviendo una referencia al objeto persistente.No pase la identificación (pk) al método persistente ni intente guardar () en lugar de persistir ().
fuente
TestEntityManager.persistAndFlush()
para hacer que una instancia sea administrada y persistente y luego sincronizar el contexto de persistencia con la base de datos subyacente. Devuelve la entidad fuente originalPara solucionar el problema, debe seguir estos pasos:
1. Eliminar la asociación de niños en cascada
Por lo tanto, debe eliminar el
@CascadeType.ALL
de la@ManyToOne
asociación. Las entidades secundarias no deben conectarse en cascada a las asociaciones principales. Solo las entidades principales deberían conectarse en cascada a las entidades secundarias.Tenga en cuenta que configuré el
fetch
atributoFetchType.LAZY
porque la búsqueda ansiosa es muy mala para el rendimiento .2. Establecer ambos lados de la asociación
Siempre que tenga una asociación bidireccional, debe sincronizar ambos lados utilizando los métodos
addChild
yremoveChild
en la entidad principal:Para obtener más detalles sobre por qué es importante sincronizar ambos extremos de una asociación bidireccional, consulte este artículo .
fuente
En su definición de entidad, no está especificando @JoinColumn para los
Account
unidos aTransaction
. Querrás algo como esto:EDITAR: Bueno, supongo que sería útil si estuvieras usando la
@Table
anotación en tu clase. Je :)fuente
Incluso si sus anotaciones se declaran correctamente para administrar adecuadamente la relación uno a muchos, aún puede encontrar esta excepción precisa. Al agregar un nuevo objeto secundario
Transaction
, a un modelo de datos adjunto, deberá administrar el valor de la clave primaria, a menos que se suponga que no lo haga . Si proporciona un valor de clave principal para una entidad secundaria declarada de la siguiente manera antes de llamarpersist(T)
, encontrará esta excepción.En este caso, las anotaciones declaran que la base de datos administrará la generación de los valores de clave principal de la entidad tras la inserción. Proporcionar uno usted mismo (como a través del configurador de Id) provoca esta excepción.
Alternativamente, pero efectivamente igual, esta declaración de anotación da como resultado la misma excepción:
Por lo tanto, no establezca el
id
valor en el código de su aplicación cuando ya se esté administrando.fuente
Si nada ayuda y sigue recibiendo esta excepción, revise su
equals()
métodos, y no incluya la colección secundaria. Especialmente si tiene una estructura profunda de colecciones incrustadas (por ejemplo, A contiene Bs, B contiene Cs, etc.).En ejemplo de
Account -> Transactions
:En el ejemplo anterior, elimine las transacciones de los
equals()
cheques. Esto se debe a que hibernar implicará que no está intentando actualizar un objeto antiguo, sino que pasa un objeto nuevo para que persista, siempre que cambie un elemento en la colección secundaria.Por supuesto, estas soluciones no se adaptarán a todas las aplicaciones y debe diseñar cuidadosamente lo que desea incluir en los métodos
equals
yhashCode
.fuente
Tal vez sea el error de OpenJPA, cuando revierte restablece el campo @Version, pero pcVersionInit se mantiene verdadero. Tengo una AbstraceEntity que declaró el campo @Version. Puedo solucionarlo restableciendo el campo pcVersionInit. Pero no es una buena idea. Creo que no funciona cuando la entidad persiste en cascada.
fuente
Debe configurar la transacción para cada cuenta.
O puede ser suficiente (si corresponde) para establecer los identificadores en nulos en muchos lados.
fuente
fuente
Mi respuesta basada en JPA de Spring Data: simplemente agregué una
@Transactional
anotación a mi método externo.Por que funciona
La entidad hija se separó de inmediato porque no había un contexto de sesión de Hibernate activo. Proporcionar una transacción Spring (Data JPA) garantiza que haya una sesión de Hibernate.
Referencia:
https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/
fuente
En mi caso, estaba cometiendo una transacción cuando se usaba el método persistir . Al cambiar el método de persistir para guardar , se resolvió.
fuente
Si las soluciones anteriores no funcionan solo una vez, comente los métodos getter y setter de la clase de entidad y no establezca el valor de id. (Clave primaria) Entonces esto funcionará.
fuente
@OneToMany (mappedBy = "xxxx", cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE}) funcionó para mí.
fuente
Se resuelve guardando el objeto dependiente antes del siguiente.
Esto me sucedió porque no estaba configurando Id (que no se generó automáticamente). y tratando de ahorrar con relación @ManytoOne
fuente