Tengo un modelo de objetos con JPA que contiene una relación de muchos a uno: un Accounttiene muchos Transactions. A Transactiontiene 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 Accountobjeto, agregarle transacciones y mantener el Accountobjeto 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 Accountque contiene transacciones, pero no una Transacción que tiene un Account. Pensé que esto se debía a que Accountpodrí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 Transactionasociado con un Accountobjeto 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.MERGElugar deCascadeType.PERSISToCascadeType.ALL.He tenido el mismo problema y
CascadeType.MERGEha funcionado para mí.Espero que estés ordenado
fuente
Elimine la cascada de la entidad secundaria
Transaction, debería ser solo:(
FetchType.EAGERse 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(Transactionen este caso). Los desconectados (u otros estados no transitorios) pueden no hacerlo (Accounten este caso, ya que está en DB).Por lo tanto, obtiene la excepción "entidad separada pasada para persistir" . ¡La
Accountentidad está destinada! No elTransactionque 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
accountobjeto utilizando la lógica de fusión, ypersistse usa para persistir objetos nuevos y se quejará si la jerarquía tiene un objeto ya persistente. Debe usarsaveOrUpdateen tales casos, en lugar depersist.fuente
mergeeltransactionobjeto obtienes el mismo error?transactionno persistió? ¿Cómo lo comprobaste? Tenga en cuenta quemergeestá 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.ALLde la@ManyToOneasociació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
fetchatributoFetchType.LAZYporque 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
addChildyremoveChilden 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
Accountunidos aTransaction. Querrás algo como esto:EDITAR: Bueno, supongo que sería útil si estuvieras usando la
@Tableanotació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
idvalor 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
equalsyhashCode.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
@Transactionalanotació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