Effective data load in JPA with Entity Graph

Advertisements

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, ManyToOney ManyToMany. Those annotations have a default fetch type with the following possible values:

AnnotationDefault fetch Type
OneToOneEager
ManyToOneEager
OneToManyLazy
ManyToManyLazy

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 Schoolrecord 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 Schoolrecords is needed or a list with hundred of Schoolis loaded. Probably, most of the cases (from business logic viewpoint) only the data from the School level is needed and not its Studentlist. 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 EntityGraphis 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.

Advertisements

Leave a Reply

Your email address will not be published. Required fields are marked *