Uso de FetchMode en Hibernate con Spring Data

Fetch Mode en Hibernate

Fetch Mode en Hibernate


En este artículo sobre el uso de FetchMode en Hibernate con Spring Data, vamos a ver con el uso de anotaciones las diferentes maneras que hay para que Hibernate traiga los datos.

En un artículo anterior vimos que con el uso de Join Fetch, los datos obtenidos de Base de Datos se realizarán en una única query.

Tipos de FetchMode con Hibernate

Existen 3 tipos diferentes de FetchMode, si navegamos en el código podemos ver que la clase es un enum:

public enum FetchMode {
  SELECT,
  JOIN,
  SUBSELECT;

  private FetchMode() {
  }
}
  • FetchMode de tipo SELECT: Con el modo SELECT Hibernate se comporta en modo LAZY, para traer las colecciones hijas.
  • Tipo JOIN: Con el FetchMode de tipo JOIN cargaremos las colecciones en una única query.
  • FetchMode de tipo SUBSELECT: Al hacer el uso del modo SUBSELECT lo que vamos a hacer es realizar una query para el padre y otra query para la colección hija.

Una vez hemos visto los 3 tipos de FetchMode que tenemos en Hibernate vamos a verlo mejor con un ejemplo.

Ejemplo de uso de FetchMode en Hibernate con Spring Data

Para nuestro ejemplo vamos a utilizar dos entidades, una entidad Department y otra Employee, en la que Department tiene una relación OneToMany con Employee.

Entidad Department

@Getter
@Setter
@Entity
public class Department {

  @Id
  @Column(name = "DEPARTMENT_ID")
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Integer departmentId;

  private String name;

  @OneToMany
  @Fetch(FetchMode.JOIN)
  private List<Employee> employees;

}

En la entidad Department nos vamos a encargar de ir definiendo los diferentes tipos de FetchMode para ir haciendo las pruebas.

Entidad Employee

@Getter
@Setter
@Entity
public class Employee {

  @Id
  @SequenceGenerator(name = "emp_seq", sequenceName = "seq_employee")
  @GeneratedValue(generator = "emp_seq")
  @Column(name = "EMPLOYEE_ID")
  private Integer id;

  private String name;

  private String surname;

  @ManyToOne
  @JoinColumn(name = "department_id")
  private Department department;

}

En función del modo establecido obtendremos los empleados con una query o n+1 query etc …

Uso de FetchMode SELECT

Cuando hacemos de FetchMode.SELECT en Hibernate, las colecciones hijas se traen de Base de Datos en modo Lazy. Es decir, se realiza una query por el padre y tantas queries como hijos hay.

Por ejemplo si en nuestra entidad department hacemos lo siguiente:

@Getter
@Setter
@Entity
public class Department {

  @Id
  @GeneratedValue(generator = "uuid")
  @GenericGenerator(name = "uuid", strategy = "uuid2")
  private String id;

  private String name;

  @OneToMany
  @Fetch(value = FetchMode.SELECT)
  private Set<Employee> employees = new HashSet<>();

}

Y generamos el siguiente test:

  @Test
  @Transactional
  public void withoutJoinFetch() {
  Department department = departmentRepository.findById(1).orElseThrow();

  log.info("Employees -> {} ", department.getEmployees());

  }

Una vez ejecutamos el test, y teniendo que tenemos un único Employee, el resultado será el siguiente:

Hibernate: 
    select
        d1_0.id,
        d1_0.name 
    from
        department d1_0
Hibernate: 
    select
        e1_0.department_id,
        e1_0.id,
        e1_0.name,
        e1_0.surname 
    from
        employee e1_0 
    where
        e1_0.department_id=?

Por lo que hemos hecho una query para el Department y n queries en función del número de Employees, en nuestro caso 1.

Podemos utilizar la anotación @Batch para traer las colecciones por lotes, por ejemplo:

  @OneToMany
  @Fetch(value = FetchMode.SELECT)
  @Batch(size=5)
  private Set<Employee> employees = new HashSet<>();

FetchMode Join en Hibernate

Al hacer uso de FetchMode.JOIN en Hibernate los datos de Base de Datos se obtienen en una única query.

Por ejemplo si cambiamos la entidad Department:

@Getter
@Setter
@Entity
public class Department {

  @Id
  @GeneratedValue(generator = "uuid")
  @GenericGenerator(name = "uuid", strategy = "uuid2")
  private String id;

  private String name;

  @OneToMany
  @Fetch(value = FetchMode.JOIN)
  private Set<Employee> employees = new HashSet<>();

}

Al ejecutar el test siguiente:

  @Test
  @Transactional
  public void withoutJoinFetch() {
  Department department = departmentRepository.findById(1).orElseThrow();

  log.info("Employees -> {} ", department.getEmployees());

  }

Podemos ver en los resultados una única query, es decir, una join entre tablas obteniendo todos los resultados de manera que no se harán queries extra para employees.

Hibernate: 
    select
        d1_0.department_id,
        e1_0.department_department_id,
        e1_1.employee_id,
        d2_0.department_id,
        d2_0.name,
        e1_1.name,
        e1_1.surname,
        d1_0.name 
    from
        department d1_0 
    left join
        (department_employees e1_0 
    join
        employee e1_1 
            on e1_1.employee_id=e1_0.employees_employee_id) 
                on d1_0.department_id=e1_0.department_department_id 
        left join
            department d2_0 
                on d2_0.department_id=e1_1.department_id 
        where
            d1_0.department_id=?

Con esta aproximación podemos ver como se realiza una JOIN para obtener todos los resultados.

Si hacemos uso de CascadeType.ALL en nuestra relación no funcionará como una JOIN y realizará varias queries.

Uso de FetchMode.SUBSELECT en Hibernate

Cuando se hace uso del modo SUBSELECT en Hibernate lo que estamos haciendo en una relación OneToMany es realizar dos queries en total, una para obtener el padre (Department) y otra para obtener la colección (Employees).

@Getter
@Setter
@Entity
public class Department {

  @Id
  @GeneratedValue(generator = "uuid")
  @GenericGenerator(name = "uuid", strategy = "uuid2")
  private String id;

  private String name;

  @OneToMany
  @Fetch(value = FetchMode.SUBSELECT)
  private Set<Employee> employees = new HashSet<>();

}

Si ejecutamos nuestro test de pruebas:

  @Test
  @Transactional
  public void withoutJoinFetch() {
  Department department = departmentRepository.findById(1).orElseThrow();

  log.info("Employees -> {} ", department.getEmployees());

  }

Vemos que el número de queries son dos, una para obtener el padre y otra en la que se hace una select para obtener todos los hijos:

Hibernate: 
    select
        d1_0.department_id,
        d1_0.name 
    from
        department d1_0 
    where
        d1_0.department_id=?
Hibernate: 
    select
        e1_0.department_department_id,
        e1_1.employee_id,
        d1_0.department_id,
        d1_0.name,
        e1_1.name,
        e1_1.surname 
    from
        department_employees e1_0 
    join
        employee e1_1 
            on e1_1.employee_id=e1_0.employees_employee_id 
    left join
        department d1_0 
            on d1_0.department_id=e1_1.department_id 
    where
        e1_0.department_department_id=?

Conclusión

En este artículo hemos visto el uso de FetchMode en Hibernate con Spring Data la cual nos va ayudar a obtener los datos en función de nuestras necesidades. Estas aproximaciones van a permitir afinar mejor la información devuelta por la Base de Datos y permitiendo una mejor optimización y rendimiento.

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 *