JPA 2.1 has introduced the Entity Graph
feature, and it’s a very useful when we need to load data from a relationship in the query, in a different way as the mapped in the entity.
Understanding the relationships
When we map a relationship between 2 tables in JPA, we use the annotations OneToOne
, OneToMany
, ManyToOne
y ManyToMany
. Those annotations have a default fetch type with the following possible values:
Annotation | Default fetch Type |
---|---|
OneToOne | Eager |
ManyToOne | Eager |
OneToMany | Lazy |
ManyToMany | Lazy |
The default fetchType of those fields can be modified, using the attribute fetchType
of the annotation, and that way provide a behavior according to our requirements. However, that is not recommend if it is not strictly required because it can provoke a non efficient or optimum performance when querying the data base.
For example, if we have a relation OneToMany
between this 2 classes
@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; }
In this case, when loading an Student
JPA is in charge to get the data for the School
of that student automatically because of the ManyToOne
relationship. On the other hand, when a School
record is fetch, JPA will not load the list of students automatically, those records will be fetch as a separate query if we try to access the field and while the school recrod still attach to the JPA context. If the school record is not attach to the JPA context, then calling the method School.getStudents()
will trigger a LazyInitializationException
.
Because of that exception, we might be tempted to change the fetchType of the attribute with the annotation OneToMany
to FetchType.Eager
. That way when a School
record is gotten the list of students is already loaded, but here is where the performance of our application starts to be impacted. Possibly, with just a few records the issue is not noticed, but if we are facing hundred of School
records and thousands of Student
records, we will notice it immediately.
This is the famous N+1 issue, for each School
record JPA will execute an extra query to get the list of Students
, therefore accessing the database N+1 times. That will be the default behavior, it doesn’t matter if only an School
records is needed or a list with hundred of School
is loaded. Probably, most of the cases (from business logic viewpoint) only the data from the School
level is needed and not its Student
list. In other words, an unnecessary and very expensive load of data was done.
Entity Graph to the rescue
Entity Graph is a feature add in JPA 2.1, it basically allows us to override the FetchType.LAZY
behavior in the relation to load the fields eagerly in a query when the EntityGraph
is used. By default, the entity will continue loading the fields in lazy mode (if the entity maps it that way), but the query will have a different behavior because of the use of the EntityGraph
.
Defining an Entity Graph
There are two way to do it, programmatically and by annotations. The easiest approach is by annotations, and in my opinion is the most efficient, because that Entity Graph
is created during the start up of the app, and the entity graph is available for future uses.
@Entity @NamedEntityGraph( name = "School.students", attributeNodes = { @NamedAttributeNode("students") }) public class School { private long id; @OneToMany(mappedBy = "school") private List<Student> students; }
That is done with the annotation @NamedEntityGraph
, a name should be indicated, and also the attributes that we need to load eagerly (instead of lazy) when the entity graph is applied to a query.
If we want to do the programatic approach, this is the way:
EntityGraph<?> entityGraph = entityManager.createEntityGraph("School.students"); entityGraph.addAttributeNodes("students");
Using the Entity Graph
The EntityGraph
is already defined, now is the moment to use it in our query. For that we will use the hint javax.persistence.loadgraph
or javax.persistence.fetchgraph
. Later, I will explain the difference between both options.
EntityGraph<?> entityGraph = entityManager.getEntityGraph("School.students"); List<School> result = entityManager .createQuery("select s from School") .setHint("javax.persistence.fetchgraph", entityGraph) .getResultList();
In case we are using Spring Data, the annotation @EntityGraph
can be used, the name of the entity-graph is mandatory and the fetch is optional with possible values FETCH
o LOAD
.
@EntityGraph(type=EntityGraphType.FETCH, value="School.students") public List<School> findBySchoolName(String name);
Fetch vs Load
There is still something we haven’t explained yet, what is the difference between using fetchgraph
or loadgraph
when we run a query with an EntityGraph. It’s important to mention that in the case of the SpringData’s annotation@EntityGraph
, the default value is FETCH
.
- Fetch: It loads eagerly only those attributes indicated in the entity graph. It means, if the entity has more attributes mapped as EAGER but not indicated as part of the Entity Graph, those attributes will be loaded in a LAZY way.
- Load: It keeps the existing fetch type already mapped in the entity. It only modifies to EAGER those fields indicated in the entity graph.
Conclusion
Now that we have knowledge of the Entity Graph feature, and understand how the FetchType.EAGER and FetchType.LAZY work, we can use them in a properly way to provide an efficient usage in our queries, avoiding the N+1 issue when the entities has relationship with another entities.