example - metodo merge jpa
JPA EntityManager: ¿Por qué usar persist() sobre merge()? (15)
JPA es indiscutiblemente una gran simplificación en el dominio de las aplicaciones empresariales creadas en la plataforma Java. Como desarrollador que tuvo que hacer frente a las complejidades de los beans de entidad anteriores en J2EE, veo la inclusión de JPA entre las especificaciones de Java EE como un gran avance. Sin embargo, al profundizar en los detalles de la APP, encuentro cosas que no son tan fáciles. En este artículo, me ocupo de la comparación de los métodos de combinación y persistencia del EntityManager cuyo comportamiento superpuesto puede causar confusión no solo para un novato. Además, propongo una generalización que considera ambos métodos como casos especiales de un método más general combinado.
Entidades persistentes
En contraste con el método de combinación, el método de persistencia es bastante sencillo e intuitivo. El escenario más común del uso del método de persistencia se puede resumir de la siguiente manera:
"Una instancia recién creada de la clase de entidad se pasa al método de persistencia. Después de que este método se devuelve, la entidad se administra y planea su inserción en la base de datos. Puede suceder en o antes de que se confirme la transacción o cuando se llama al método de vaciado. Si la entidad hace referencia a otra entidad a través de una relación marcada con la estrategia de cascada PERSIST, este procedimiento también se aplica a ella ".
La especificación entra más en los detalles, sin embargo, recordarlos no es crucial ya que estos detalles cubren solo situaciones más o menos exóticas.
Fusionar entidades
En comparación con la persistencia, la descripción del comportamiento de la fusión no es tan simple. No hay un escenario principal, como lo es en el caso de persistir, y un programador debe recordar todos los escenarios para escribir un código correcto. Me parece que los diseñadores de JPA querían tener algún método cuya preocupación principal fuera el manejo de entidades separadas (como lo opuesto al método de persistencia que se ocupa principalmente de las entidades recién creadas). La principal tarea del método de combinación es transferir el estado desde un entidad no administrada (pasada como argumento) a su contraparte administrada dentro del contexto de persistencia. Esta tarea, sin embargo, se divide en varios escenarios que empeoran la inteligibilidad del comportamiento del método en general.
En lugar de repetir párrafos de la especificación JPA, preparé un diagrama de flujo que describe esquemáticamente el comportamiento del método de combinación:
Entonces, ¿cuándo debo usar persistir y cuándo combinar?
persistir
- Desea que el método siempre cree una nueva entidad y nunca actualice una entidad. De lo contrario, el método lanza una excepción como consecuencia de la violación de la unicidad de la clave principal.
- Procesos por lotes, manejo de las entidades de forma continua (ver patrón de pasarela).
- Optimización del rendimiento
unir
- Desea que el método inserte o actualice una entidad en la base de datos.
- Desea manejar las entidades de una manera sin estado (objetos de transferencia de datos en servicios)
- Desea insertar una nueva entidad que puede tener una referencia a otra entidad que puede pero no puede haber sido creada todavía (la relación debe estar marcada como MERGE). Por ejemplo, al insertar una nueva foto con una referencia a un álbum nuevo o preexistente.
EntityManager.merge()
puede insertar nuevos objetos y actualizar los existentes.
¿Por qué querría uno usar persist()
(que solo puede crear nuevos objetos)?
Algunos detalles más sobre la combinación que le ayudarán a usar la combinación sobre la persistencia:
Devolver una instancia administrada que no sea la entidad original es una parte crítica del proceso de combinación. Si ya existe una instancia de entidad con el mismo identificador en el contexto de persistencia, el proveedor sobrescribirá su estado con el estado de la entidad que se está fusionando, pero la versión administrada que ya existía debe devolverse al cliente para que pueda ser usado. Si el proveedor no actualizó la instancia de Empleado en el contexto de persistencia, cualquier referencia a esa instancia será inconsistente con el nuevo estado que se está fusionando.
Cuando se invoca merge () en una nueva entidad, se comporta de manera similar a la operación persist (). Agrega la entidad al contexto de persistencia, pero en lugar de agregar la instancia de la entidad original, crea una nueva copia y administra esa instancia en su lugar. La copia que se crea mediante la operación merge () se conserva como si se invocara el método persist ().
En presencia de relaciones, la operación de combinación () intentará actualizar la entidad administrada para que apunte a las versiones administradas de las entidades referenciadas por la entidad separada. Si la entidad tiene una relación con un objeto que no tiene una identidad persistente, el resultado de la operación de fusión no está definido. Algunos proveedores pueden permitir que la copia administrada apunte al objeto no persistente, mientras que otros pueden lanzar una excepción inmediatamente. La operación de fusión () puede ser opcionalmente en cascada en estos casos para evitar que ocurra una excepción. Cubriremos en cascada la operación de fusión () más adelante en esta sección. Si una entidad que se fusiona apunta a una entidad eliminada, se lanzará una excepción IllegalArgumentException.
Las relaciones de carga lenta son un caso especial en la operación de combinación. Si una relación de carga lenta no se activó en una entidad antes de que se separara, esa relación se ignorará cuando la entidad se fusione. Si la relación se activó mientras se administró y luego se estableció en nulo mientras la entidad se desconectó, la versión administrada de la entidad también se borrará de la relación durante la fusión ".
Toda la información anterior fue tomada de "Pro JPA 2 Mastering the Java ™ Persistence API" por Mike Keith y Merrick Schnicariol. Capítulo 6. Sección de desprendimiento y fusión. Este libro es en realidad un segundo libro dedicado a JPA por los autores. Este nuevo libro tiene mucha información nueva que la anterior. Realmente recomiendo leer este libro para aquellos que estarán seriamente involucrados con JPA. Lamento haber publicado anónimamente mi primera respuesta.
De cualquier manera agregará una entidad a un PersistenceContext, la diferencia está en lo que hace con la entidad después.
Persist toma una instancia de entidad, la agrega al contexto y hace que esa instancia sea administrada (es decir, se realizará un seguimiento de las futuras actualizaciones de la entidad).
Fusionar crea una nueva instancia de su entidad, copia el estado de la entidad suministrada y hace que la nueva copia sea administrada. La instancia que transmita no se administrará (los cambios que realice no formarán parte de la transacción, a menos que vuelva a llamar combinación).
Tal vez un ejemplo de código ayudará.
MyEntity e = new MyEntity();
// scenario 1
// tran starts
em.persist(e);
e.setSomeField(someValue);
// tran ends, and the row for someField is updated in the database
// scenario 2
// tran starts
e = new MyEntity();
em.merge(e);
e.setSomeField(anotherValue);
// tran ends but the row for someField is not updated in the database
// (you made the changes *after* merging)
// scenario 3
// tran starts
e = new MyEntity();
MyEntity e2 = em.merge(e);
e2.setSomeField(anotherValue);
// tran ends and the row for someField is updated
// (the changes were made to e2, not e)
Los escenarios 1 y 3 son aproximadamente equivalentes, pero hay algunas situaciones en las que querría usar el escenario 2.
Encontré esta explicación de los documentos de Hibernate esclarecedor, porque contienen un caso de uso:
El uso y la semántica de merge () parecen ser confusos para los nuevos usuarios. En primer lugar, siempre que no intente utilizar el estado de objeto cargado en un administrador de entidades en otro administrador de entidades nuevo, no debería necesitar usar merge () en absoluto . Algunas aplicaciones completas nunca utilizarán este método.
Normalmente se usa merge () en el siguiente escenario:
- La aplicación carga un objeto en el primer gestor de entidades.
- El objeto se pasa a la capa de presentación.
- Se hacen algunas modificaciones al objeto.
- el objeto se pasa de nuevo a la capa de lógica de negocios
- la aplicación persiste estas modificaciones llamando a merge () en un segundo administrador de entidades
Aquí está la semántica exacta de merge ():
- Si hay una instancia administrada con el mismo identificador actualmente asociado con el contexto de persistencia, copie el estado del objeto dado en la instancia administrada
- Si no hay una instancia administrada asociada actualmente con el contexto de persistencia, intente cargarla desde la base de datos o cree una nueva instancia administrada
- se devuelve la instancia administrada
- la instancia dada no se asocia con el contexto de persistencia, permanece separada y generalmente se descarta
De: http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html
Es posible que haya venido aquí para recibir consejos sobre cuándo usar persistir y cuándo usar fusionar . Creo que depende de la situación: qué tan probable es que necesite crear un nuevo registro y qué tan difícil es recuperar los datos persistentes.
Supongamos que puede utilizar una clave / identificador natural.
Los datos deben persistir, pero de vez en cuando existe un registro y se solicita una actualización. En este caso, puede intentar una persistencia y si lanza una EntityExistsException, la busca y combina los datos:
prueba {entityManager.persist (entidad)}
catch (excepción EntityExistsException) {/ * recuperar y combinar * /}
Los datos persistentes deben actualizarse, pero de vez en cuando todavía no hay registros para los datos. En este caso, lo busca y persiste si falta la entidad:
entity = entityManager.find (clave);
if (entity == null) {entityManager.persist (entidad); }
else {/ * merge * /}
Si no tiene una clave / identificador natural, tendrá más dificultades para averiguar si la entidad existe o no, o cómo buscarla.
Las fusiones también pueden tratarse de dos maneras:
- Si los cambios son generalmente pequeños, aplíquelos a la entidad administrada.
- Si los cambios son comunes, copie el ID de la entidad persistente, así como los datos no alterados. Luego llame a EntityManager :: merge () para reemplazar el contenido anterior.
Escenario X:
Tabla: Spitter (Uno), Tabla: Spittles (Muchos) (Spittles es el propietario de la relación con un FK: spitter_id)
Este escenario permite guardar: el Spitter y ambos Spittles como si fueran propiedad de Same Spitter.
Spitter spitter=new Spitter();
Spittle spittle3=new Spittle();
spitter.setUsername("George");
spitter.setPassword("test1234");
spittle3.setSpittle("I love java 2");
spittle3.setSpitter(spitter);
dao.addSpittle(spittle3); // <--persist
Spittle spittle=new Spittle();
spittle.setSpittle("I love java");
spittle.setSpitter(spitter);
dao.saveSpittle(spittle); //<-- merge!!
Escenario Y:
Esto salvará a Spitter, salvará a 2 Spittles ¡Pero no harán referencia al mismo Spitter!
Spitter spitter=new Spitter();
Spittle spittle3=new Spittle();
spitter.setUsername("George");
spitter.setPassword("test1234");
spittle3.setSpittle("I love java 2");
spittle3.setSpitter(spitter);
dao.save(spittle3); // <--merge!!
Spittle spittle=new Spittle();
spittle.setSpittle("I love java");
spittle.setSpitter(spitter);
dao.saveSpittle(spittle); //<-- merge!!
Estaba obteniendo excepciones de lazyLoading en mi entidad porque estaba tratando de acceder a una colección cargada de forma perezosa que estaba en sesión.
Lo que haría fue en una solicitud por separado, recuperar la entidad de la sesión y luego intentar acceder a una colección en mi página jsp que fue problemática.
Para aliviar esto, actualicé la misma entidad en mi controlador y se la pasé a mi jsp, aunque me imagino que cuando vuelva a guardar en la sesión también estará accesible a través de SessionScope
y no lanzar una LazyLoadingException
, una modificación del ejemplo 2:
Lo siguiente me ha funcionado:
// scenario 2 MY WAY
// tran starts
e = new MyEntity();
e = em.merge(e); // re-assign to the same entity "e"
//access e from jsp and it will work dandy!!
Hay algunas diferencias más entre la merge
y la persist
(enumeraré de nuevo las que ya se han publicado aquí):
D1. merge
no hace que la entidad pasada se administre, sino que devuelve otra instancia que se administra. persist
en el otro lado hará que la entidad pasada se gestione:
//MERGE: passedEntity remains unmanaged, but newEntity will be managed
Entity newEntity = em.merge(passedEntity);
//PERSIST: passedEntity will be managed after this
em.persist(passedEntity);
D2. Si elimina una entidad y luego decide persistir en la entidad, puede hacerlo solo con persist (), porque la merge
arrojará una IllegalArgumentException
.
D3. Si decidió ocuparse manualmente de sus ID (por ejemplo, utilizando UUID), entonces una operación de merge
activará consultas SELECT
posteriores para buscar entidades existentes con ese ID, mientras que es posible que persist
no necesite esas consultas.
D4. Hay casos en los que simplemente no confía en el código que llama a su código, y para asegurarse de que no se actualicen los datos, sino que se inserte, debe usar persist
.
La especificación JPA dice lo siguiente sobre persist()
.
Si X es un objeto separado, se puede
EntityExistsException
laEntityExistsException
cuando se invoca la operación de persistencia, o laEntityExistsException
u otraPersistenceException
se pueden lanzar al momento de la descarga o de confirmación.
Por lo tanto, usar persist()
sería adecuado cuando el objeto no debería ser un objeto separado. Es posible que prefiera que el código lance la PersistenceException
para que falle rápidamente.
Aunque la especificación no es clara , persist()
podría establecer el @GeneratedValue
@Id
para un objeto. Sin embargo, merge()
debe tener un objeto con el @Id
ya generado.
Noté que cuando usé em.merge
, obtuve una instrucción SELECT
para cada INSERT
, incluso cuando no había ningún campo que JPA estuviera generando para mí; el campo de clave principal era un UUID que yo mismo configuré. em.persist(myEntityObject)
a em.persist(myEntityObject)
y obtuve solo las INSERT
.
Otra observación:
merge()
solo le importará un ID generado automáticamente (probado en IDENTITY
y SEQUENCE
) cuando ya existe un registro con dicho ID en su tabla. En ese caso, merge()
intentará actualizar el registro. Sin embargo, si un ID está ausente o no coincide con ningún registro existente, merge()
lo ignorará por completo y le pedirá a un db que asigne uno nuevo. Esto es a veces una fuente de muchos errores. No use merge()
para forzar una identificación para un nuevo registro.
persist()
el contrario, persist()
nunca te permitirá pasarle un ID. Fallará de inmediato. En mi caso, es:
Causado por: org.hibernate.PersistentObjectException: la entidad separada pasó a persistir
hibernate-jpa javadoc tiene una pista:
Emite : javax.persistence.EntityExistsException - si la entidad ya existe. (Si la entidad ya existe, la EntityExistsException puede ser lanzada cuando se invoca la operación de persistencia, o la EntityExistsException u otra PersistenceException puede ser lanzada en el momento de la descarga o de confirmación).
Persistir y fusionar son para dos propósitos diferentes (no son alternativas en absoluto).
(editado para ampliar las diferencias de información)
persistir:
- Insertar un nuevo registro en la base de datos.
- Adjuntar el objeto al administrador de la entidad.
unir:
- Encuentra un objeto adjunto con el mismo id y actualízalo.
- Si existe, actualice y devuelva el objeto ya adjunto.
- Si no existe inserte el nuevo registro en la base de datos.
persistencia () eficiencia:
- Podría ser más eficiente para insertar un nuevo registro en una base de datos que merge ().
- No duplica el objeto original.
Persistente () semántica:
- Se asegura de que esté insertando y no actualizando por error.
Ejemplo:
{
AnyEntity newEntity;
AnyEntity nonAttachedEntity;
AnyEntity attachedEntity;
// Create a new entity and persist it
newEntity = new AnyEntity();
em.persist(newEntity);
// Save 1 to the database at next flush
newEntity.setValue(1);
// Create a new entity with the same Id than the persisted one.
AnyEntity nonAttachedEntity = new AnyEntity();
nonAttachedEntity.setId(newEntity.getId());
// Save 2 to the database at next flush instead of 1!!!
nonAttachedEntity.setValue(2);
attachedEntity = em.merge(nonAttachedEntity);
// This condition returns true
// merge has found the already attached object (newEntity) and returns it.
if(attachedEntity==newEntity) {
System.out.print("They are the same object!");
}
// Set 3 to value
attachedEntity.setValue(3);
// Really, now both are the same object. Prints 3
System.out.println(newEntity.getValue());
// Modify the un attached object has no effect to the entity manager
// nor to the other objects
nonAttachedEntity.setValue(42);
}
De esta manera solo existe 1 objeto adjunto para cualquier registro en el administrador de entidades.
Fusionar () para una entidad con un ID es algo como:
AnyEntity myMerge(AnyEntity entityToSave) {
AnyEntity attached = em.find(AnyEntity.class, entityToSave.getId());
if(attached==null) {
attached = new AnyEntity();
em.persist(attached);
}
BeanUtils.copyProperties(attached, entityToSave);
return attached;
}
Aunque si está conectado a MySQL, merge () podría ser tan eficiente como persist () usando una llamada a INSERT con la opción ACTUALIZACIÓN DE LA TECLA DUPLICADA, JPA es una programación de muy alto nivel y no puede asumir que este será el caso en todas partes.
Repasando las respuestas, faltan algunos detalles con respecto a ''Cascade'' y la generación de id. Ver pregunta
Además, vale la pena mencionar que puede tener anotaciones de Cascade
separadas para fusionar y persistir: Cascade.MERGE
y Cascade.PERSIST
que se tratarán de acuerdo con el método utilizado.
La especificación es tu amiga;)
Si está utilizando el generador asignado, el uso de la combinación en lugar de la persistencia puede causar una declaración SQL redundante , lo que afectará el rendimiento.
Además, llamar a la combinación para las entidades administradas también es un error, ya que las entidades administradas son administradas automáticamente por Hibernate y su estado se sincroniza con el registro de la base de datos mediante el mecanismo de comprobación sucia al vaciar el contexto de persistencia .
Para comprender cómo funciona todo esto, primero debe saber que Hibernate cambia la mentalidad de desarrollador de las declaraciones de SQL a las transiciones de estado de entidad .
Una vez que una entidad es administrada activamente por Hibernate, todos los cambios se propagarán automáticamente a la base de datos.
Hibernate supervisa entidades actualmente vinculadas. Pero para que una entidad se administre, debe estar en el estado de entidad correcto.
Primero, debemos definir todos los estados de la entidad:
Nuevo (Transitorio)
Un objeto creado recientemente que nunca se ha asociado con una
Session
hibernación (también conocido comoPersistence Context
) y no se asigna a ninguna fila de la tabla de la base de datos se considera que está en el estado Nuevo (Transitorio).Para persistir necesitamos llamar explícitamente al método de
EntityManager#persist
o hacer uso del mecanismo de persistencia transitiva.Persistente (Gestionado)
Se ha asociado una entidad persistente con una fila de la tabla de la base de datos y está siendo administrada por el contexto de persistencia actual. Cualquier cambio realizado en dicha entidad será detectado y propagado a la base de datos (durante el tiempo de descarga de la sesión). Con Hibernate, ya no tenemos que ejecutar las instrucciones INSERT / UPDATE / DELETE. Hibernate emplea un estilo de trabajo de escritura diferida transaccional y los cambios se sincronizan en el último momento responsable, durante el tiempo de descarga de la
Session
actual.Separado
Una vez que el contexto de persistencia en ejecución actual se cierra, todas las entidades previamente administradas se desconectan. Los cambios sucesivos ya no serán rastreados y no se realizará ninguna sincronización automática de la base de datos.
Para asociar una entidad separada a una sesión de Hibernate activa, puede elegir una de las siguientes opciones:
Reinserción
Hibernate (pero no JPA 2.1) admite la reincorporación a través del método de actualización de la sesión #. Una sesión de hibernación solo puede asociar un objeto de entidad para una fila de base de datos determinada. Esto se debe a que el contexto de persistencia actúa como un caché en memoria (caché de primer nivel) y solo un valor (entidad) está asociado a una clave dada (tipo de entidad e identificador de base de datos). Una entidad se puede volver a conectar solo si no hay otro objeto JVM (que coincida con la misma fila de la base de datos) ya asociado a la sesión de Hibernate actual.
Fusionando
La combinación copiará el estado de la entidad separada (origen) en una instancia de entidad administrada (destino). Si la entidad fusionada no tiene equivalente en la sesión actual, se buscará una de la base de datos. La instancia del objeto separado continuará siendo desconectada incluso después de la operación de combinación.
Remoto
Aunque JPA exige que solo se pueda eliminar las entidades administradas, Hibernate también puede eliminar entidades separadas (pero solo a través de una llamada al método de eliminación de sesión #). Una entidad eliminada solo está programada para su eliminación y la instrucción DELETE de la base de datos real se ejecutará durante el tiempo de descarga de la sesión.
Para comprender mejor las transiciones de estado JPA, puede visualizar el siguiente diagrama:
O si usa la API específica de Hibernate:
la persistencia (entidad) se debe usar con entidades totalmente nuevas, para agregarlas a la base de datos (si la entidad ya existe en la base de datos, se lanzará la excepción EntityExistsException).
se debe utilizar la combinación (entidad) para devolver la entidad al contexto de persistencia si la entidad se separó y se modificó.
Probablemente persistir está generando la instrucción INSERT sql y fusionar la instrucción UPDATE sql (pero no estoy seguro).