Uso de FetchMode en Hibernate con Spring Data
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!