Ciclo de vida en Hibernate
Actualmente lo más normal es hacer uso de Hibernate para la persistencia de nuestros objetos en Base de Datos, es por eso que es bastante importante conocer el ciclo de vida en Hibernate y evitar posibles errores.
¿Qué es Hibernate?
Hibernate es un ORM, es decir, una herramienta de mapeo relacional de objetos entre la Base de Datos y Java. Permite realizar el mapeo de objetos de dominio a objetos de una Base de Datos relacional.
Estados de ciclo de vida en Hibernate y Persistence Context
Cada entidad en Hibernate tiene su ciclo de vida:
- Transient
- Managed
- Detached
- Deleted
A parte de los estados de Hibernate un concepto que debemos entender y tener en cuenta es el Persistence Context.
El Persistence Context se encarga de realizar un seguimiento de todos los datos cargados y sincronizar cualquier cambio con las base de datos al final de la transacción.
El uso de Persitence Context son implementados por JPA EntityManager y por la Session de Hibernate, por lo que el Persistence Context se relaciona con los diferentes estados del ciclo de vida de Hibernate.
Estado Transient en el ciclo de vida de Hibernate
Un estado transient en el ciclo de vida de Hibernate es aquel en el que el objeto no se encuentra gestionado por la Session de Hibernate y no se encuentra persistido.
Para que un objeto en estado transient pueda ser persistido podemos hacer uso de merge() o persist.. Si usas versiones de Hibernate igual o menor a la 5 podrás hacer uso de save o saveOrUpdate, en la 6 se encuentra deprecado.
Managed en ciclo de vida de Hibernate
Una entidad en estado managed es una representación de una fila en la base de datos. Aunque pueda ser que físicamente no se encuentre todavía en Base de Datos porque todavía no ha sido persistido. Una entidad en estado Managed se encuentra dentro de la sesión de Hibernate, y cualquier cambio que se realiza en este estado es propagado a la Base de Datos.
Vamos a ver un ejemplo para entenderlo mejor:
@Getter @Setter @Entity public class Department { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String name; }
A continuación vamos a cargar en la Session el departamento llamado Human Resource. Para ello nos apoyamos en la Session de Hibernate:
Session session = sessionFactory.openSession(); Query query = session.createQuery("Select d from Department d where name= :name", Department.class); query.setParameter("name", "Human Resource"); department = (Department) query.getSingleResult(); Transaction transaction = session.getTransaction(); transaction.begin(); department.setName("Stuff"); transaction.commit();
Al hacer uso de commit se sincroniza el estado con la base de datos, una entidad en estado Managed es siempre una entidad que puede ser persistida, por lo que para guardar el cambio en base de datos únicamente con commit o flush se realizar. No tenemos que llamar a ningún método más o cambiar de estado la entidad.
Estado Detach Entity en el ciclo de vida en Hibernate
Una entidad en estado Detach representa nuestro objeto de la aplicación con una fila de la Base de Datos, difiere en del estado Manage que no tiene ningún seguimiento con el persistence context. Es decir, el estado será Detach cuando la session ha sido cerrada o se ha llamado a clear o evict.
Este es un de los típicos errores en Hibernate que ocurre cuando tenemos nuestra entidad en estado detach: org.hibernate.PersistentObjectException` to JPA `PersistenceException` : detached entity passed to persist
Vamos a partir de nuestra entidad Department para conseguir una entidad en estado Detach, para ello vamos a hacer uso de evict().
@Test public void given_department_when_detach_a_single_to_change_name_then_not_found() { Session session = sessionFactory.openSession(); Query query = session.createQuery("Select d from Department d where name= :name", Department.class); query.setParameter("name", "Human Resource"); department = (Department) query.getSingleResult(); session.evict(singleDepartment); department.setName("Accounts"); session.getTransaction().commit(); //No hay persistencia porque se encuentra en estado detach Query query = session.createQuery("Select d from Department d where name= :name", Department.class); query.setParameter("name", "Accounts"); department = (Department) query.getSingleResult(); Exception exception = assertThrows(NoResultException.class, () -> { query.getSingleResult() }); String expectedMessage = "No result found for query [Select d from Department d where name= :name]"; String actualMessage = exception.getMessage(); assertTrue(actualMessage.contains(expectedMessage)); }
Si queremos que se guarde tendríamos que hacer uso de merge() para volver a introducirnos en la Session.
@Test public void given_department_when_detach_a_single_to_change_name_then_update() { Session session = sessionFactory.openSession(); Query query = session.createQuery("Select d from Department d where name= :name", Department.class); query.setParameter("name", "Human Resource"); department = (Department) query.getSingleResult(); session.evict(singleDepartment); department.setName("Accounts"); session.merge(department); session.getTransaction().commit(); Query query = session.createQuery("Select d from Department d where name= :name", Department.class); query.setParameter("name", "Accounts"); department = (Department) query.getSingleResult(); assertTrue("Accounts".equalsIgnoreCase(department.getName())); }
Estado delete en Hibernate
Una entidad se encuentra en estado delete cuando el método remove(entity) es invocado. La entidad será eliminada cuando se haga el commit de la Session.
@Test public void given_deparment_when_remove_from_session_then_department_is_removed() { session.beginTransaction(); Query query = session.createQuery("Select e from Department e where id= :id", Department.class); query.setParameter("id", department.getId()); department = (Department) query.getSingleResult(); session.remove(department); session.getTransaction().commit(); List<Department> departments = session.createQuery("Select d from Department d", Department.class).list(); assertEquals(departments.size(), 0); }
En el test anterior hemos borrado tanto de la Session de Hibernate como de la BBDD.
Conclusión
En esta entrada hemos visto los diferentes estados del Ciclo de vida en Hibernate, así como ejemplos de cada estado. Entender bien los estados por los que pueden pasar nuestras entidades de Hibernate nos ayudará a solventar errores y optimizar nuestra aplicación.
Si necesitas más información puedes escribirnos un comentario o un correo electrónico a refactorizando.web@gmail.com o también nos puedes contactar por nuestras redes sociales Facebook o twitter y te ayudaremos encantados!