Hibernate: save, persist, update, merge, saveOrUpdate

Introducción

En este artículo discutiremos las diferencias entre varios métodos de la interfaz de sesión: save, persist, update, merge, saveOrUpdate.

Esto no es una introducción a Hibernar y ya debería conocer los conceptos básicos de configuración, asignación de objetos relacionales y trabajo con instancias de entidades. Para obtener un artículo introductorio sobre Hibernación, visite nuestro tutorial sobre Hibernación 4 con Primavera.

Más información:

Eliminar objetos con Hibernación

Guía rápida para eliminar una entidad en Hibernación.
Leer más →

Procedimientos almacenados con Hibernación

En este artículo se explica brevemente cómo llamar a procedimientos de almacenamiento desde Hibernación.
Leer más →

Descripción general de los identificadores en Hibernate / JPA

Aprenda a asignar identificadores de entidad con Hibernate.
Leer más →

Implementación de contexto de persistencia de sesión

La interfaz de sesión tiene varios métodos que eventualmente resultan en guardar datos en la base de datos: persist, save, update, merge, saveOrUpdate. Para entender la diferencia entre estos métodos, primero debemos discutir el propósito de la Sesión como un contexto de persistencia y la diferencia entre los estados de las instancias de entidad en relación con la Sesión.

También debemos entender la historia del desarrollo de Hibernación que condujo a algunos métodos de API parcialmente duplicados.

2.1. Administración de instancias de entidad

Aparte de la asignación de objetos relacionales en sí, uno de los problemas que Hibernate pretendía resolver es el problema de la administración de entidades durante el tiempo de ejecución. La noción de «contexto de persistencia» es la solución de Hibernación a este problema. El contexto de persistencia se puede considerar como un contenedor o una caché de primer nivel para todos los objetos que cargó o guardó en una base de datos durante una sesión.

La sesión es una transacción lógica, cuyos límites están definidos por la lógica de negocio de la aplicación. Cuando trabaje con la base de datos a través de un contexto de persistencia y todas las instancias de entidad estén asociadas a este contexto, siempre debe tener una sola instancia de entidad para cada registro de base de datos con el que haya interactuado durante la sesión.

En Hibernación, el contexto de persistencia está representado por org.hibernación.Instancia de sesión. Para JPA, es el javax.persistencia.EntityManager. Cuando usamos Hibernate como proveedor de JPA y operamos a través de la interfaz EntityManager, la implementación de esta interfaz básicamente envuelve el objeto de sesión subyacente. Sin embargo, la Sesión de Hibernación proporciona una interfaz más rica con más posibilidades, por lo que a veces es útil trabajar con la sesión directamente.

2.2. Estados de Instancias de entidad

Cualquier instancia de entidad de su aplicación aparece en uno de los tres estados principales en relación con el contexto de persistencia de sesión:

  • transitorio: esta instancia no está, y nunca estuvo, adjunta a una sesión; esta instancia no tiene filas correspondientes en la base de datos; por lo general, es solo un objeto nuevo que ha creado para guardarlo en la base de datos;
  • persistente: esta instancia está asociada a un objeto de sesión único; al vaciar la Sesión en la base de datos, se garantiza que esta entidad tendrá un registro coherente correspondiente en la base de datos;
  • separado: esta instancia se adjuntó una vez a una Sesión (en estado persistente), pero ahora no lo está; una instancia entra en este estado si la expulsa del contexto, borra o cierra la sesión, o somete la instancia a un proceso de serialización/deserialización.

Aquí hay un diagrama de estados simplificado con comentarios sobre los métodos de sesión que hacen que las transiciones de estado ocurran.

2016-07-11_13-38-11

Cuando la instancia de entidad está en estado persistente, todos los cambios que realice en los campos asignados de esta instancia se aplicarán a los registros y campos de la base de datos correspondientes al vaciar la sesión. La instancia persistente se puede considerar «en línea», mientras que la instancia separada se ha» desconectado » y no se supervisa para detectar cambios.

Esto significa que cuando cambia los campos de un objeto persistente, no tiene que llamar a guardar, actualizar o a ninguno de esos métodos para obtener estos cambios en la base de datos: todo lo que necesita es confirmar la transacción, o vaciar o cerrar la sesión, cuando haya terminado con ella.

2.3. La conformidad con la especificación JPA

Hibernate fue la implementación de Java OR más exitosa. No es de extrañar que la especificación de Java persistence API (JPA) estuviera fuertemente influenciada por la API de Hibernación. Desafortunadamente, también había muchas diferencias: algunas importantes, otras más sutiles.

Para actuar como una implementación del estándar JPA, las API de Hibernación tuvieron que ser revisadas. Se agregaron varios métodos a la interfaz de sesión para que coincidiera con la interfaz EntityManager. Estos métodos tienen el mismo propósito que los métodos» originales», pero se ajustan a la especificación y, por lo tanto, tienen algunas diferencias.

Diferencias Entre las operaciones

Es importante comprender desde el principio que todos los métodos (persist, save, update, merge, saveOrUpdate) no dan lugar inmediatamente a las instrucciones SQL UPDATE o INSERT correspondientes. El almacenamiento real de datos en la base de datos se produce al confirmar la transacción o al vaciar la sesión.

Los métodos mencionados básicamente administran el estado de las instancias de entidad al hacer una transición entre diferentes estados a lo largo del ciclo de vida.

Como una entidad de ejemplo, usaremos una persona de entidad con asignación de anotaciones simple:

@Entitypublic class Person { @Id @GeneratedValue private Long id; private String name; // ... getters and setters}

3.1. Persist

El método persist está diseñado para agregar una nueva instancia de entidad al contexto de persistencia, es decir, hacer la transición de una instancia de estado transitorio a persistente.

Normalmente lo llamamos cuando queremos agregar un registro a la base de datos (persistir una instancia de entidad):

Person person = new Person();person.setName("John");session.persist(person);

¿Qué sucede después de llamar al método persist? El objeto person ha pasado de un estado transitorio a un estado persistente. El objeto se encuentra ahora en el contexto de persistencia, pero aún no se ha guardado en la base de datos. La generación de instrucciones INSERT solo se producirá al confirmar la transacción, vaciar o cerrar la sesión.

Observe que el método persist tiene el tipo de retorno void. Opera en el objeto pasado» en su lugar», cambiando su estado. La variable person hace referencia al objeto persistente real.

Este método es una adición posterior a la interfaz de sesión. La principal característica diferenciadora de este método es que se ajusta a la especificación JSR-220 (persistencia EJB). La semántica de este método está estrictamente definida en la especificación, que básicamente establece que:

  • una instancia transitoria se vuelve persistente (y la operación cae en cascada a todas sus relaciones con cascade=PERSIST o cascade=ALL),
  • si una instancia ya es persistente, esta llamada no tiene efecto para esta instancia en particular (pero sigue cayendo en cascada a sus relaciones con cascade=PERSIST o cascade=ALL),
  • si una instancia está separada, debe esperar una excepción, ya sea al llamar a este método, o al confirmar o limpiar la sesión.

Observe que no hay nada aquí que tenga que ver con el identificador de una instancia. La especificación no indica que el id se generará de inmediato, independientemente de la estrategia de generación de id. La especificación para el método persist permite a la implementación emitir instrucciones para generar id al confirmar o vaciar, y no se garantiza que el id no sea nulo después de llamar a este método, por lo que no debe confiar en él.

Puede llamar a este método en una instancia ya persistente y no sucede nada. Pero si intenta mantener una instancia separada, la implementación está obligada a lanzar una excepción. En el siguiente ejemplo persistimos la entidad, la desalojamos del contexto para que se separe, y luego tratamos de persistir de nuevo. La segunda llamada a la sesión.persist() causa una excepción, por lo que el siguiente código no funcionará:

Person person = new Person();person.setName("John");session.persist(person);session.evict(person);session.persist(person); // PersistenceException!

3.2. Guardar

El método guardar es un método de hibernación «original» que no se ajusta a la especificación JPA.

Su propósito es básicamente el mismo que persist, pero tiene diferentes detalles de implementación. La documentación de este método indica estrictamente que persiste la instancia, «primero asignando un identificador generado». Se garantiza que el método devuelve el valor serializable de este identificador.

Person person = new Person();person.setName("John");Long id = (Long) session.save(person);

El efecto de guardar una instancia ya persistida es el mismo que con persist. La diferencia se produce cuando intenta guardar una instancia separada:

Person person = new Person();person.setName("John");Long id1 = (Long) session.save(person);session.evict(person);Long id2 = (Long) session.save(person);

La variable id2 diferirá de id1. La llamada a guardar en una instancia separada crea una nueva instancia persistente y le asigna un nuevo identificador, lo que resulta en un registro duplicado en una base de datos al confirmar o limpiar.

3.3. Merge

La intención principal del método merge es actualizar una instancia de entidad persistente con nuevos valores de campo desde una instancia de entidad separada.

Por ejemplo, supongamos que tiene una interfaz RESTful con un método para recuperar un objeto serializado JSON por su id al autor de la llamada y un método que recibe una versión actualizada de este objeto del autor de la llamada. Una entidad que pasó por dicha serialización / deserialización aparecerá en un estado de separación.

Después de deserializar esta instancia de entidad, debe obtener una instancia de entidad persistente de un contexto de persistencia y actualizar sus campos con nuevos valores de esta instancia separada. Así que el método de fusión hace exactamente eso:

  • busca una instancia de entidad por id tomada del objeto pasado (se recupera una instancia de entidad existente del contexto de persistencia o se carga una nueva instancia de la base de datos);
  • copia los campos del objeto pasado a esta instancia;
  • devuelve la instancia recién actualizada.

En el siguiente ejemplo desalojamos (separamos) la entidad guardada del contexto, cambiamos el campo nombre y, a continuación, fusionamos la entidad separada.

Person person = new Person(); person.setName("John"); session.save(person);session.evict(person);person.setName("Mary");Person mergedPerson = (Person) session.merge(person);

Tenga en cuenta que el método merge devuelve un objeto: es el objeto mergedPerson que se cargó en el contexto de persistencia y se actualizó, no el objeto person que pasó como argumento. Esos son dos objetos diferentes, y el objeto person generalmente necesita ser descartado (de todos modos, no cuente con que esté unido al contexto de persistencia).

Al igual que con el método persist, JSR-220 especifica el método merge para que tenga cierta semántica en la que pueda confiar:

  • si la entidad está separada, se copia en una entidad persistente existente;
  • si la entidad es transitoria, se copia en una entidad persistente de nueva creación;
  • esta operación se realiza en cascada para todas las relaciones con la asignación cascade=MERGE o cascade=ALL;
  • si la entidad es persistente, esta llamada al método no tiene efecto sobre ella (pero la cascada todavía tiene lugar).

3.4. Actualizar

Al igual que con persist and save, el método de actualización es un método de hibernación «original» que estaba presente mucho antes de que se agregara el método de fusión. Su semántica difiere en varios puntos clave:

  • actúa sobre el objeto pasado (su tipo de retorno es void); el método update hace una transición del objeto pasado de un estado separado a un estado persistente;
  • este método lanza una excepción si se le pasa una entidad transitoria.

En el siguiente ejemplo guardamos el objeto, luego lo desahuciamos (separamos) del contexto, luego cambiamos su nombre y llamamos a update. Observe que no colocamos el resultado de la operación de actualización en una variable separada, porque la actualización tiene lugar en el objeto person en sí. Básicamente, estamos reensamblando la instancia de entidad existente al contexto de persistencia, algo que la especificación de JPA no nos permite hacer.

Person person = new Person();person.setName("John");session.save(person);session.evict(person);person.setName("Mary");session.update(person);

Intentar llamar a update en una instancia transitoria dará lugar a una excepción. La siguiente no funcionará:

Person person = new Person();person.setName("John");session.update(person); // PersistenceException!

3.5. saveOrUpdate

Este método solo aparece en la API de Hibernación y no tiene su contraparte estandarizada. Al igual que update, también se puede usar para volver a unir instancias.

En realidad, la clase interna DefaultUpdateEventListener que procesa el método update es una subclase de DefaultSaveOrUpdateListener, simplemente anula alguna funcionalidad. La principal diferencia del método saveOrUpdate es que no genera excepciones cuando se aplica a una instancia transitoria; en su lugar, hace que esta instancia transitoria sea persistente. El siguiente código persistirá en una instancia de Person recién creada:

Person person = new Person();person.setName("John");session.saveOrUpdate(person);

Puede pensar en este método como una herramienta universal para hacer que un objeto sea persistente independientemente de su estado, ya sea transitorio o separado.

¿Qué Usar?

Si no tiene ningún requisito especial, como regla general, debe atenerse a los métodos persist y merge, ya que están estandarizados y se garantiza que se ajustan a la especificación JPA.

También son portátiles en caso de que decida cambiar a otro proveedor de persistencia, pero a veces pueden parecer no tan útiles como los métodos de hibernación «originales», save, update y saveOrUpdate.

Conclusión

Hemos discutido el propósito de diferentes métodos de sesión de Hibernación en relación con la administración de entidades persistentes en tiempo de ejecución. Hemos aprendido cómo estos métodos transisten las instancias de entidades a lo largo de sus ciclos de vida y por qué algunos de estos métodos han duplicado la funcionalidad.



+