JPA EntityManager: ¿Por qué usar persist () sobre merge ()?

EntityManager.merge() puede insertar nuevos objetos y actualizar los existentes.

¿Por qué querría uno usar persist() (que solo puede crear objetos nuevos)?

De cualquier manera agregará una entidad a PersistenceContext, la diferencia está en lo que haga con la entidad después.

Persist toma una instancia de entidad, la agrega al contexto y la hace administrar (es decir, las futuras actualizaciones de la entidad se rastrearán).

Merge 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 ingrese no se administrará (los cambios que realice no serán parte de la transacción, a menos que llame de nuevo a la combinación).

Tal vez un ejemplo de código ayude.

 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 desearía usar el escenario 2.

Persistir y fusionar son para dos propósitos diferentes (no son alternativas en absoluto).

(editado para expandir la información de diferencias)

persistir:

  • Insertar un nuevo registro en la base de datos
  • Adjunte el objeto al administrador de la entidad.

unir:

  • Encuentre un objeto adjunto con el mismo ID y actualícelo.
  • Si existe, actualice y devuelva el objeto ya adjunto.
  • Si no existe, inserte el nuevo registro en la base de datos.

persistir () eficiencia:

  • Podría ser más eficiente para insertar un nuevo registro en una base de datos que merge ().
  • No duplica el objeto original.

persistencia () 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.

merge () para una entidad con una identificación es algo así 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 ON DUPLICATE KEY UPDATE, JPA es una progtwigción de muy alto nivel y no puede suponer que va a ser así en todas partes.

Si está utilizando el generador asignado, usar fusionar en lugar de persistir puede provocar una instrucción SQL redundante , lo que afecta el rendimiento.

Además, la combinación de llamada para entidades administradas también es un error, ya que Hibernate administra las entidades administradas automáticamente y su estado se sincroniza con el registro de la base de datos mediante el mecanismo de comprobación sucia al enjuagar el Contexto de persistencia .

Para comprender cómo funciona todo esto, primero debe saber que Hibernate cambia la mentalidad del desarrollador de las declaraciones de SQL a las transiciones de estado de la entidad .

Una vez que Hibernate gestiona activamente una entidad, todos los cambios se propagarán automáticamente a la base de datos.

Hibernate monitorea las entidades adjuntas actualmente. 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 recién creado que no haya sido asociado con una Session Hibernación (también conocido como Persistence Context ) y que no esté mapeado en 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 EntityManager#persist o hacer uso del mecanismo de persistencia transitiva.

  • Persistente (Administrado)

    Una entidad persistente se ha asociado con una fila de tabla de base de datos y está siendo administrada por el contexto de persistencia en ejecución actual. Cualquier cambio realizado en dicha entidad se detectará y se propagará a la base de datos (durante el tiempo de descarga de la sesión). Con Hibernate, ya no tenemos que ejecutar instrucciones INSERT / UPDATE / DELETE. Hibernate emplea un estilo de trabajo transaccional de escritura retrasada y los cambios se sincronizan en el último momento responsable, durante el tiempo de actividad de la Session actual.

  • Separado

    Una vez que se cierra el Contexto de Persistencia en ejecución actual, todas las entidades administradas previamente se separan. Los cambios sucesivos ya no se rastrearán y no habrá 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:

    • Volver a unir

      Hibernate (pero no JPA 2.1) admite volver a conectar a través del método de actualización Session #. Una sesión de Hibernate solo puede asociar un objeto de entidad para una fila de base de datos dada. 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 determinada (tipo de entidad e identificador de base de datos). Una entidad puede volverse a vincular 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.

    • Fusión

    La fusión va a copiar el estado de entidad separada (fuente) a una instancia de entidad gestionada (destino). Si la entidad fusionada no tiene equivalente en la sesión actual, se obtendrá uno de la base de datos. La instancia del objeto separado continuará estando desconectada incluso después de la operación de fusión.

  • Remoto

    Aunque JPA exige que solo se permita eliminar entidades gestionadas, 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á progtwigda para borrarse y la statement DELETE de la base de datos real se ejecutará durante el tiempo de instalación de la sesión.

Para comprender mejor las transiciones de estado JPA, puede visualizar el siguiente diagtwig:

enter image description here

O si usa la API específica de Hibernate:

enter image description here

Noté que cuando usaba em.merge , obtenía una instrucción SELECT para cada INSERT , incluso cuando no había ningún campo que JPA generara para mí: el campo de la clave principal era un UUID que yo mismo configuré. em.persist(myEntityObject) a em.persist(myEntityObject) y obtuve solo las INSERT .

La especificación JPA dice lo siguiente sobre persist() .

Si X es un objeto separado, la EntityExistsException puede lanzarse cuando se invoca la EntityExistsException , o la EntityExistsException u otra PersistenceException pueden lanzarse al momento o comprometer el tiempo.

Entonces, usar persist() sería adecuado cuando el objeto no debería ser un objeto separado. Puede preferir que el código arroje la PersistenceException para que falle rápidamente.

Aunque la especificación no está clara , persist() podría establecer @GeneratedValue @Id para un objeto. merge() sin embargo debe tener un objeto con @Id ya generado.

Más detalles sobre la fusió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 fusió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 Employee en el contexto de persistencia, cualquier referencia a esa instancia será incoherente con el nuevo estado que se fusiona.

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 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 merge () intentará actualizar la entidad gestionada para que apunte a las versiones administradas de las entidades a las que hace referencia la entidad separada. Si la entidad tiene una relación con un objeto que no tiene 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 de inmediato. La operación merge () se puede conectar opcionalmente en cascada en estos casos para evitar que ocurra una excepción. Cubriremos la cascada de la operación merge () más adelante en esta sección. Si una entidad fusionada apunta a una entidad eliminada, se lanzará una excepción IllegalArgumentException.

Las relaciones de carga diferida son un caso especial en la operación de fusión. Si no se desencadenó una relación de carga diferida en una entidad antes de que se separara, esa relación se ignorará cuando la entidad se fusione. Si la relación se desencadenó mientras se administraba y luego se estableció en nulo mientras la entidad estaba separada, la versión administrada de la entidad también tendrá la relación despejada 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. Desprendimiento y fusión de secciones. Este libro es en realidad un segundo libro dedicado a JPA por los autores. Este nuevo libro tiene mucha información nueva, luego la anterior. Realmente recomiendo leer este libro para aquellos que estarán seriamente involucrados con JPA. Lamento haber publicado anónimamente mi primera respuesta.

Hay algunas diferencias más entre merge y persist (enumeraré de nuevo las que ya se publicaron 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 sea administrada:

 //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 con la entidad, puede hacerlo solo con persist (), ya que merge arrojará una IllegalArgumentException .

D3. Si decidió cuidar sus identificaciones manualmente (por ejemplo, mediante el uso de UUID), una operación de merge desencadenará consultas SELECT posteriores para buscar entidades existentes con esa ID, mientras que persist puede no necesitar 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 inserten, debe usar persist .

Recibía excepciones de carga lenta en mi entidad porque estaba intentando acceder a una colección cargada que estaba en sesión.

Lo que haría sería solicitar por separado, recuperar la entidad de la sesión y luego tratar de acceder a una colección en mi página jsp que era problemática.

Para aliviar esto, actualicé la misma entidad en mi controlador y se la pasé a mi jsp, aunque imagino que cuando vuelva a guardarla en sesión, también estará accesible a través de SessionScope y no arrojará 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!! 

Repasando las respuestas, faltan algunos detalles sobre ‘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;)

Escenario X:

Tabla: Spitter (Uno), Tabla: Spittles (Muchos) (Spittles es el propietario de la relación con un FK: spitter_id)

Este escenario da como resultado el ahorro: 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á al Spitter, salvará a los 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!! 

Encontré esta explicación de los documentos de Hibernate esclarecedora, porque contienen un caso de uso:

El uso y la semántica de merge () parece ser confuso para los nuevos usuarios. En primer lugar, siempre que no intente utilizar el estado del objeto cargado en un administrador de entidades en otro nuevo administrador de entidades, no debería necesitar usar fusionar () en absoluto . Algunas aplicaciones completas nunca usarán este método.

Habitualmente, merge () se usa en el siguiente escenario:

  • La aplicación carga un objeto en el primer administrador de entidades
  • el objeto se pasa a la capa de presentación
  • algunas modificaciones se hacen al objeto
  • el objeto se devuelve a la capa de lógica de negocios
  • la aplicación persiste estas modificaciones llamando a merge () en un segundo administrador de entidad

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 ninguna instancia gestionada asociada actualmente con el contexto de persistencia, intente cargarla desde la base de datos o cree una nueva instancia gestionada
  • la instancia administrada es devuelta
  • 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

JPA es indiscutiblemente una gran simplificación en el dominio de las aplicaciones empresariales basadas en la plataforma Java. Como desarrollador que tuvo que lidiar con las complejidades de los beans de entidad antigua en J2EE, veo la inclusión de JPA entre las especificaciones Java EE como un gran salto adelante. Sin embargo, mientras profundizo en los detalles de JPA encuentro cosas que no son tan fáciles. En este artículo me ocupo de la comparación de los métodos de fusión y persistencia de EntityManager cuyo comportamiento superpuesto puede causar confusión no solo a un novato. Además propongo una generalización que combina ambos métodos como casos especiales de un método más general.

Entidades persistentes

En contraste con el método de fusión, el método persist es bastante directo e intuitivo. El escenario más común del uso del método persist se puede resumir de la siguiente manera:

“Una instancia recién creada de la clase de entidad se pasa al método persist. Después de que este método retorna, la entidad se gestiona y planifica para su inserción en la base de datos. Puede suceder en o antes de que la transacción se comprometa o cuando se llame al método flush. Si la entidad hace referencia a otra entidad a través de una relación marcada con la estrategia de cascada PERSIST, también se aplica este procedimiento “.

enter image description here

La especificación va más en detalles, sin embargo, recordarlos no es crucial, ya que estos detalles cubren situaciones más o menos exóticas.

Fusionando entidades

En comparación con persistir, la descripción del comportamiento de la fusión no es tan simple. No existe un escenario principal, como es el caso de la persistencia, y un progtwigdor 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 principal preocupación sería manejar entidades separadas (como lo contrario al método persist que trata principalmente con las entidades recién creadas). La principal tarea del método de fusión es transferir el estado desde un entidad no administrada (pasada como el argumento) a su contraparte administrada dentro del contexto de persistencia. Sin embargo, esta tarea se divide aún más en varios escenarios que empeoran la inteligibilidad del comportamiento general del método.

En lugar de repetir párrafos de la especificación JPA, he preparado un diagtwig de flujo que representa esquemáticamente el comportamiento del método de fusión:

enter image description here

Entonces, ¿cuándo debería usar persist y cuándo fusionar?

persistir

  • Desea que el método siempre cree una nueva entidad y nunca actualice una entidad. De lo contrario, el método arroja una excepción como consecuencia de la violación de unicidad de clave primaria.
  • Procesos por lotes, manejo de entidades de manera con estado (ver patrón de Gateway).
  • Optimización del rendimiento

unir

  • Desea que el método inserte o actualice una entidad en la base de datos.
  • Desea manejar entidades de forma apátrida (objetos de transferencia de datos en servicios)
  • Desea insertar una nueva entidad que pueda tener una referencia a otra entidad que puede pero aún no puede haber sido creada (la relación debe estar marcada como MERGE). Por ejemplo, insertar una nueva foto con una referencia a un álbum nuevo o uno preexistente.

persist (entidad) debe usarse con entidades totalmente nuevas, para agregarlas a DB (si la entidad ya existe en DB, habrá throw EntityExistsException).

merge (entity) debe usarse para volver a poner a la entidad en contexto de persistencia si la entidad se separó y se modificó.

Probablemente persistir es generar instrucción INSERT sql y fusionar la instrucción UPDATE sql (pero no estoy seguro).

Es posible que haya venido aquí en busca de 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 datos persistentes.

Supongamos que puede usar 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, podría intentar una persistencia y si arroja una EntityExistsException, la busca y combina los datos:

    try {entityManager.persist (entidad)}

    catch (excepción EntityExistsException) {/ * recuperar y fusionar * /}

  • Los datos persistentes deben actualizarse, pero de vez en cuando no hay registro de los datos. En este caso, lo busca y persiste si la entidad falta:

    entity = entityManager.find (clave);

    if (entidad == nulo) {entityManager.persist (entidad); }

    else {/ * fusionar * /}

Si no tiene una clave / identificador natural, tendrá más dificultades para determinar si la entidad existe o no, o cómo buscarla.

Las fusiones también se pueden abordar de dos maneras:

  1. Si los cambios son usualmente pequeños, aplíquelos a la entidad administrada.
  2. Si los cambios son comunes, copie la ID de la entidad persistente, así como los datos inalterados. Luego, llame a EntityManager :: merge () para reemplazar el contenido anterior.

Otra observación:

merge() solo se preocupará por un ID generado automáticamente (probado en IDENTITY y SEQUENCE ) cuando ya exista un registro con dicho ID en su tabla. En ese caso, merge() intentará actualizar el registro. Sin embargo, si no hay un id o no coincide con ninguno de los registros existentes, merge() lo ignorará por completo y le pedirá a db que asigne uno nuevo. Esto a veces es una fuente de muchos errores. No use merge() para forzar una identificación para un nuevo registro.

persist() por otro lado, nunca te dejará pasar una identificación. Fallará de inmediato. En mi caso, es:

Causado por: org.hibernate.PersistentObjectException: entidad separada pasada para persistir

hibernate-jpa javadoc tiene una pista:

Lanza : javax.persistence.EntityExistsException – si la entidad ya existe. (Si la entidad ya existe, la EntityExistsException puede lanzarse cuando se invoca la operación persist, o la EntityExistsException u otra PersistenceException pueden lanzarse al momento del enjuague o de compromiso).

    Intereting Posts