1. Introducción
Hibernate es un marco conveniente para administrar datos persistentes, pero comprender cómo funciona internamente puede ser complicado a veces.
En este tutorial, aprenderemos sobre los estados de los objetos y cómo movernos entre ellos. También veremos los problemas que podemos encontrar con las entidades separadas y cómo resolverlos.
2. Sesión de Hibernate
La interfaz de sesión es la herramienta principal que se utiliza para comunicarse con Hibernate. Proporciona una API que nos permite crear, leer, actualizar y eliminar objetos persistentes. La sesión tiene un ciclo de vida simple. Lo abrimos, realizamos algunas operaciones y luego lo cerramos.
Cuando operamos sobre los objetos durante la sesión , se adjuntan a esa sesión . Los cambios que realizamos se detectan y guardan al cerrar. Después de cerrar, Hibernate rompe las conexiones entre los objetos y la sesión.
3. Estados de objeto
En el contexto de la sesión de Hibernate , los objetos pueden estar en uno de tres estados posibles: transitorio, persistente o separado.
3.1. Transitorio
Un objeto que no hemos adjuntado a ninguna sesión se encuentra en estado transitorio. Como nunca se persistió, no tiene ninguna representación en la base de datos. Como ninguna sesión lo sabe, no se guardará automáticamente.
Creemos un objeto de usuario con el constructor y confirmemos que no es administrado por la sesión:
Session session = openSession(); UserEntity userEntity = new UserEntity("John"); assertThat(session.contains(userEntity)).isFalse();
3.2. Persistente
Un objeto que hemos asociado con una sesión está en estado persistente. Lo guardamos o lo leemos desde un contexto de persistencia, por lo que representa alguna fila en la base de datos.
Creemos un objeto y luego usemos el método persistente para hacerlo persistente:
Session session = openSession(); UserEntity userEntity = new UserEntity("John"); session.persist(userEntity); assertThat(session.contains(userEntity)).isTrue();
Alternativamente, podemos usar el método de guardar . La diferencia es que el método de persistencia solo guardará un objeto, y el método de guardar generará adicionalmente su identificador si es necesario.
3.3. Separado
Cuando cerramos la sesión , todos los objetos de su interior se desprenden. Aunque todavía representan filas en la base de datos, ya no las administra ninguna sesión :
session.persist(userEntity); session.close(); assertThat(session.isOpen()).isFalse(); assertThatThrownBy(() -> session.contains(userEntity));
A continuación, aprenderemos cómo guardar entidades transitorias y separadas.
4. Guardar y volver a adjuntar una entidad
4.1. Guardar una entidad transitoria
Vamos a crear una nueva entidad y guardarla en la base de datos. Cuando construimos el objeto por primera vez, estará en estado transitorio.
Para mantener nuestra nueva entidad, usaremos el método de persistencia :
UserEntity userEntity = new UserEntity("John"); session.persist(userEntity);
Ahora, crearemos otro objeto con el mismo identificador que el primero. Este segundo objeto es transitorio porque aún no está administrado por ninguna sesión , pero no podemos hacerlo persistente usando el método persist . Ya está representado en la base de datos, por lo que no es realmente nuevo en el contexto de la capa de persistencia.
En su lugar, usaremos el método de fusión para actualizar la base de datos y hacer que el objeto sea persistente :
UserEntity onceAgainJohn = new UserEntity("John"); session.merge(onceAgainJohn);
4.2. Guardar una entidad separada
Si cerramos la sesión anterior , nuestros objetos estarán en un estado separado. De manera similar al ejemplo anterior, están representados en la base de datos, pero actualmente ninguna sesión los administra . Podemos hacerlos persistentes nuevamente usando el método de fusión :
UserEntity userEntity = new UserEntity("John"); session.persist(userEntity); session.close(); session.merge(userEntity);
5. Entidades anidadas
Las cosas se complican más cuando consideramos entidades anidadas. Digamos que nuestra entidad de usuario también almacenará información sobre su gerente:
public class UserEntity { @Id private String name; @ManyToOne private UserEntity manager; }
Cuando guardamos esta entidad, debemos pensar no solo en el estado de la entidad en sí, sino también en el estado de la entidad anidada. Creemos una entidad de usuario persistente y luego establezcamos su administrador:
UserEntity userEntity = new UserEntity("John"); session.persist(userEntity); UserEntity manager = new UserEntity("Adam"); userEntity.setManager(manager);
Si intentamos actualizarlo ahora, obtendremos una excepción:
assertThatThrownBy(() -> { session.saveOrUpdate(userEntity); transaction.commit(); });
java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.baeldung.states.UserEntity.manager -> com.baeldung.states.UserEntity
Eso está sucediendo porque Hibernate no sabe qué hacer con la entidad anidada transitoria.
5.1. Entidades anidadas persistentes
Una forma de resolver este problema es persistir explícitamente las entidades anidadas:
UserEntity manager = new UserEntity("Adam"); session.persist(manager); userEntity.setManager(manager);
Luego, después de confirmar la transacción, podremos recuperar la entidad guardada correctamente:
transaction.commit(); session.close(); Session otherSession = openSession(); UserEntity savedUser = otherSession.get(UserEntity.class, "John"); assertThat(savedUser.getManager().getName()).isEqualTo("Adam");
5.2. Operaciones en cascada
Las entidades anidadas transitorias pueden persistir automáticamente si configuramos la propiedad en cascada de la relación correctamente en la clase de entidad:
@ManyToOne(cascade = CascadeType.PERSIST) private UserEntity manager;
Ahora, cuando persistimos el objeto, esa operación se conectará en cascada a todas las entidades anidadas:
UserEntityWithCascade userEntity = new UserEntityWithCascade("John"); session.persist(userEntity); UserEntityWithCascade manager = new UserEntityWithCascade("Adam"); userEntity.setManager(manager); // add transient manager to persistent user session.saveOrUpdate(userEntity); transaction.commit(); session.close(); Session otherSession = openSession(); UserEntityWithCascade savedUser = otherSession.get(UserEntityWithCascade.class, "John"); assertThat(savedUser.getManager().getName()).isEqualTo("Adam");
6. Resumen
En este tutorial, echamos un vistazo más de cerca a cómo funciona la sesión Hibernate con respecto al estado del objeto. Luego inspeccionamos algunos problemas que puede crear y cómo resolverlos.
Como siempre, el código fuente está disponible en GitHub.