Ciclo de Vida en Hibernate

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!


Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *