Lazy loading of relations between entities is a well established best practice in JPA. Its main goal is to retrieve only the requested entities from the database and load the related entities only if needed. That is a great approach, if we only need the requested entities. But it creates additional work and can be the cause of performance problems, if we also need some of the related entities.
Lets have a look at the different ways to trigger the initialization and their specific advantages and disadvantages.
1. Call a method on the mapped relation
Lets start with the most obvious and unfortunately also the most inefficient approach. We use the find method on the EntityManager and call a method on the relation.
This code works perfectly fine, is easy to read and often used. So what is the problem with it? Well, you probably know it. This code performs an additional query to initialize the relation. That doesn’t sound like a real problem but lets calculate the number of performed queries in a more real world-ish scenario. Lets say we have an entity with 5 relations which we need to initialize. So we will get 1 + 5 = 6 queries. OK, that are 5 additional queries. That still doesn’t seem like a huge issue. But our application will be used by more than one user in parallel (I hope). Lets say our system has to server 100 parallel users. Then we will get 100 + 5*100 = 600 queries. OK, it should be obvious that this approach provides a working but not a good solution. Sooner or later, the number of additionally performed queries will slow our application down. Therefore we should try to avoid this approach and have a look at some other options.
2. Fetch Join in JPQL
A better option to initialize lazy relations is to use a JPQL query with a fetch join.
This tells the entity manager to fetch the selected entity and the relation within the same query. The advantages and disadvantages of this approach are obvious:
The advantage is that everything is fetched within one query. From a performance point of view, this is much better than the first approach.
And the main disadvantage is that we need to write additional code which executes the query. But it gets even worse, if the entity has multiple relations and we need to initialize different relations for different use cases. In this case we need to write a query for every required combination of fetch joined relations. This can become quite messy.
Using fetch joins in JPQL statements can require a huge number of queries, which will make it difficult to maintain the code base. So before we start to write lots of queries, we should think about the number of different fetch join combinations we might need. If the number is low, then this is a good approach to limit the number of performed queries.
3. Fetch Join in Criteria API
OK, this approach is basically the same as the one before. But this time we are using the Criteria API instead of the JPQL query.
The advantages and disadvantages are the same as for the JPQL query with a fetch join. The entity and the relation are retrieved with one query from the database and we need specific code for every combination of relationships. But we often already have lots of use cases specific query code, if we are using the Criteria API. So this might not be a huge issue. If we are already using the Criteria API to build the query, this is a good approach to reduce the amount of performed queries.
4. Named Entity Graph
Named entity graphs are a new feature of JPA 2.1. It can be used to define a graph of entities that shall be queried from the database. The definition of an entity graph is done via annotations and is independent of the query. If you are not familiar with this feature, you can have a look at one of my former blog posts where I covered it in more detail.
The named entity graph can then be used by the find method of the EntityManager.
This is basically an improved version of our first approach. The entity manager will retrieve the defined graph of entities from the database with one query. The only disadvantage is, that we need to annotate a named entity graph for each combination of relations that shall be retrieved within one query. We will need less additional annotations as in our second approach, but it still can become quite messy. Therefore named entity graphs are a great solution, if we only need to define a limited amount of them and reuse them for different use cases. Otherwise the code will become hard to maintain.
5. Dynamic Entity Graph
The dynamic entity graph is similar to the named entity graph and was also explained in one of the former posts. The only difference is, that the entity graph is defined via a Java API.
The definition via an API can be an advantage and a disadvantage. If we need lots of use case specific entity graphs, it might be better to define the entity graph within the specific Java code and to not add an additional annotation to the entity. This avoids entities with dozens of annotations. On the other hand, the dynamic entity graph requires more code and an additional method to be reusable.
So I recommend to use dynamic entity graphs, if we need to define a use case specific graph, that will not be reused. If we want to reuse the entity graph, it is easier to annotate a named entity graph.
- Initializing a lazy relation via calling a method on a mapped relation causes an additional query. This should be avoided for performance reasons.
- Fetch joins in JPQL statements reduce the number of queries to one but we might need a lot of different queries.
- The Criteria API also supports fetch joins and we need specific code for each combination of relations that shall be initialized.
- Named entity graphs are a good solution, if we will reuse the defined graph in our code.
- Dynamic entity graphs can be the better solution, if we need to define a use case specific graph.