Entity Graph con JPA en Spring Boot
En este nuevo artículo sobre Entity Graph con JPA en Spring Boot, vamos a ver como podemos mejorar nuestra queries y ayudar el rendimiento de nuestra aplicación. Si quieres ampliar tu conocimiento sobre queries con Spring Boot puedes echar un ojo a este artículo Criteria Queries en Spring Data.
El uso de Entity Graph es a veces desconocido entre los que desarrollamos aplicaciones y además hacemos uso de JPA, esta característica fue introducida en la versión 2.1 y nos va a permitir definir una plantilla para poder agrupar todos los campos que están persistidos y relacionados que vamos a querer recuperar de nuestra base de datos.
¿Cómo funciona Entity Graph en JPA?
Hasta que Enity Graph fue introducido en JPA, necesitabamos hacer uso de FetchType.LAZY o FetchType.EAGER para cargar nuestras colecciones. Lo que ocasionaba que en muchas casos cuando usabamos LAZY, en la que los datos se cargan según se necesitan, tuviesemos n +1 queries.
Por eso, para conseguir mejorar el rendimiento de las queries cuando tienen colecciones asociadas, fue introducida esta característica. Básicamente lo que hace JPA, es cargar todo el grafo para hacer una única query y así evitar las queries de las relaciones asociadas.
Ejemplo de Entity Graph en JPA
Crear el modelo
Para empezar vamos a crear el modelo del que será nuestro ejemplo:
@Getter @Setter @Entity public class Car { @Id private Long id; private String color; private String model; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn private User user; }
@Entity @Getter @Setter public class User { @Id private Long id; private String name; private String email; @OneToMany(mappedBy = "car") private List<Car> cars = new ArrayList<>(); }
Hemos definido dos tablas que se relacionan entre si mediante una relación oneToMany con un FetchType LAZY.
Hay que tener en cuenta que tenemos dos estrategias para las colecciones:
- EAGER: Se carga las relaciones asociadas. Las relaciones @Basic, @ManyToMany y @OneToOne se comportan así por defecto.
- LAZY: Se carga cuando se accede. Es el comportamiento por defecto de @OneToMany, @ManyToMany y @ElementCollection.
Resultado relación LAZY sin Graph Entity
Si ejecutamos tal y como esta montado, la anterior relación vamos a tener el problema de que se han producido n + 1 queries. Es por lo que para este tipo de casos vamos a usar JPA Entity Graph, que nos permitirá definir un grafo y sus atributos para cargar en una única query.
Las queries que se ejecutan serán las siguientes:
select user0_.id as id1_1_, user0_.email as email2_1_, user0_.name as name3_1_ from user user0_ where user0_.name=? select user0_.id as id1_1_, user0_.email as email2_1_, user0_.name as name3_1_ from user user0_ where user0_.name=?
Al ejecutar el código anterior nos encontramos con la situación que se producen n +1 Queries. En nuestro caso se van a ejecutar 3 queries, un para obtener los users, y otras dos para cada grupo de cars. El problema es que cuantos más datos tengamos más queries se harán y peor será el rendimiento. Vamos a mejorarlo con Graph Entity.
Definir un Graph Entity
Para definir un Graph Entity vamos a hacer uso de la anotación @NamedEntityGraph, la cual nos permite especificar los atributos que queremos cargar en la entidad y en las relaciones.
@NamedEntityGraph( name = "user-entity-graph", attributeNodes = { @NamedAttributeNode("name"), @NamedAttributeNode("email"), @NamedAttributeNode("cars"), } ) @Entity @Getter @Setter public class User { @Id private Long id; private String name; private String email; @OneToMany(mappedBy = "car") private List<Car> cars = new ArrayList<>(); }
Activar Graph Entity en el repositorio
@Repository public interface UserRepository extends CrudRepository<User, Long> { @EntityGraph(value = "user-entity-graph", type = EntityGraphType.LOAD) List<User> findByName(String name); }
Añadimos @EntityGraph a la query en la que queremos que se active en el repositorio.
Resultado relación LAZY con Graph Entity
Al ejecutar la misma aplicación pero con este pequeño cambio, podremos ver que el resultado es una única query que funciona como una join, con lo que hemos mejorado el tiempo y el rendimiento.
Al aplicar NamedEntityGraph se creará una consulta con JOIN sobre la otra tabla con lo que el rendimiento será mejor.
select user0_.id as id1_1_0_, cars1_.id as id1_0_1_, user0_.email as email2_1_0_, user0_.name as name3_1_0_, cars1_.color as color2_0_1_, cars1_.model as model3_0_1_, cars1_.user_id as user_id4_0_1_, cars1_.user_id as user_id4_0_0__, cars1_.id as id1_0_0__ from user user0_ left outer join car cars1_ on user0_.id=cars1_.user_id where user0_.name=?
Conclusión
Cuando estamos desarrollando una aplicación que accede a Base de Datos, son muy importantes los tiempos para devolver la información, por lo que las queries que realicemos tienen que ser lo más óptimas posible, por ese motivo es importante el uso de Entity Graph.
Otros artículos que te pueden interesar:
Criteria queries con Spring Data
Implementando una Cache con Spring
Spring Boot Actuator con Prometheus y Grafana
Muy útil no conocía esta funcionalidad de JPA y ha mejorado mi rendimiento