JPA 2.1 ha incluido la característica Entity Graph
, y es de gran utilidad para cuando debemos de cargar los datos de una relación en nuestra consulta de una manera diferente a la mapeada en la entidad.
Entendamos las relaciones
Cuando hacemos un mapeo de una relación entre 2 tablas en JPA, hacemos uso de las anotaciones OneToOne
, OneToMany
, ManyToOne
y ManyToMany
. Dichas anotaciones tienes un tipo de carga por defecto que puede ser Eager
o Lazy
, es decir.
Anotación | Fetch Type por defecto |
---|---|
OneToOne | Eager |
ManyToOne | Eager |
OneToMany | Lazy |
ManyToMany | Lazy |
El fetchType de los atributos que mapean una relación puede ser modificado, usando el atributo fetchType
de la anotación y de esa manera dar un comportamiento diferente al por defecto. Sin embargo, no es recomendable cambiar dicha carga si no es estrictamente necesario ya que puede derivar en un rendimiento no ópitmo en las consultas a base de datos.
Por ejemplo, si tenemos una relación OneToMany
entre dos clases
@Entity public class Student { private long id; private String name; @ManyToOne @JoinColumn(name = "school_id") private School school; } @Entity public class School { private long id; @OneToMany(mappedBy = "school") private List<Student> students; }
En este caso, a obtener un Student
JPA se va a encargar de obtener los respectivos datos de School
para dicho estudiante de manera automática, esto debido a que la relación es del tipo ManyToOne
. Por otro lado, al obtener un dato de School
no vamos a cargar automáticamente todos sus estudiantes, estos serán obtenidos en una consulta por aparte mientras dicho elemento de School
este asociado al “contexto” de JPA. Si el objeto ya no está asociado, entonces al llamar al método School.getStudents()
vamos a obtener una excepción LazyInitializationException
.
Debido a esa excepción, nos podemos ver tentados a cambiar el atributo fetchType de la anotación OneToMany
por FetchType.Eager
. De esta manera al obtener un registro School
ya se obtiene la lista de estudiantes con datos, pero es aquí donde el rendimiento de la aplicación comienza a ser impactado, posiblemente con unos pocos datos no lo notaremos, pero si nos enfrentamos a cientos de School
cada uno con miles de Student
lo notaremos de inmediato.
Para cada registro de School
JPA va a realizar una consulta extra para obtener el listado de Student
, estaremos entonces realizando N+1 accesos a la base de datos. Ahora bien, este sería el comportamiento por defecto, no importaría si lo que se necesita es solamente un registro de School
o una lista con cientos de registros. Posiblemente en la mayoría de estos usos desde el punto de vista de lógica de negocio solo se necesite la información de la clase School
y no la lista de Student
. Es decir, se hizo una carga innecesaria y además ineficiente de datos.
Entity Graph al rescate
Entity Graph es una caracteristica agregada en JPA 2.1, básicamente nos permite sobreescribir el FetchType.LAZY
de la relación para cargar tempranamente los dichos datos cuando el EntityGraph
es aplicado en una consulta. Por defecto la entidad seguirá cargando datos de manera Lazy
a no ser que la consulta explicitamente utilice el EntityGraph
.
Definiendo el Entity Graph
Se puede hacer de dos maneras, programáticamente y por anotaciones. La manera más sencilla es por anotaciones, y en mi opinión la más eficiente, ya que dicho Entity Graph
es creado una vez al iniciar la aplicación y queda disponible para futuros usos.
@Entity @NamedEntityGraph( name = "School.students", attributeNodes = { @NamedAttributeNode("students") }) public class School { private long id; @OneToMany(mappedBy = "school") private List<Student> students; }
Hacemos uso entonces de la anotación @NamedEntityGraph
, declaramos un nombre, y los atributos que queremos cargar de forma inmediata (en lugar de lazy) cuando la utilicemos.
Ahora bien, si lo queremos hacer de manera programada, entonces podemos usar lo siguiente:
EntityGraph<?> entityGraph = entityManager.createEntityGraph("School.students"); entityGraph.addAttributeNodes("students");
Usando el Entity Graph
Ya tenemos el EntityGraph
definido, ahora es momento de utilizarlo en nuestra consulta, para lo que haremos uso del hint javax.persistence.loadgraph
o javax.persistence.fetchgraph
. Más adelante explicaré la diferencia entre ambas opciones.
EntityGraph<?> entityGraph = entityManager.getEntityGraph("School.students"); List<School> result = entityManager .createQuery("select s from School") .setHint("javax.persistence.fetchgraph", entityGraph) .getResultList();
En el caso de estar usando SpringData, se puede hacer uso de la anotación @EntityGraph
, en donde se indica el nombre del entity graph creado previamente, y en tipo de cargar Fetch
o Load
.
@EntityGraph(type=EntityGraphType.FETCH, value="School.students") public List<School> findBySchoolName(String name);
Fetch vs Load
Nos queda aún una duda, qué implica usar fetchgraph
o usar loadgraph
al realizar la consulta con un entity graph. Cabe indicar que en el caso de la anotación @EntityGraph
utilizada en SpringData, tiene por defecto FETCH
como tipo.
- Fetch: Carga de manera inmediata solamente aquellos atributos indicados en la entidad. Es decir, que si la entidad tiene más atributos de relación que cargan de manera EAGER, estos serán cargados de manera LAZY.
- Load: Mantiene el tipo carga existente en las relaciones de la entidad, y solamente modifica a EAGER aquellos atributos indicados en el entity graph.
Conclusión
Ahora que conocemos de la existencia de los Entity Graph, y entendemos el funcionamiento de los FetchType.EAGER y FetchType.LAZY, podemos emplearlos de manera adecuada para proporcionar un uso eficiente a nuestras consultas a base de datos, evitar los problemas N+1 en nuestras consultas cuando tenemos entidades que se relaciones con otras entidades.
Buen post compañero.
Muchas gracias, espero haya sido de ayuda ..