Lazy loading of associations 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 you only need the requested entities. But it creates additional work and can be the cause of performance problems if you also need some of the related entities.
Let’s 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
Let’s start with the most obvious and unfortunately also the most inefficient approach. You 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 let’s calculate the number of executed queries in a more real world-ish scenario. Let’s say you have an entity with 5 associations that you need to initialize. So you 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).
Let’s say your system has to serve 100 parallel users. Then you will get 100 + 5*100 = 600 queries. That is called the n+1 select issue, and it should be obvious that this is not a good approach. Sooner or later, the number of additionally performed queries will slow your application down. Therefore you 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 associations is to use a JPQL query with a fetch join.
That tells the entity manager to load the selected entity and the relation within the same query. The advantages and disadvantages of this approach are obvious:
The advantage is that Hibernate fetches everything within one query. From a performance point of view, this is much better than the first approach.
And the main disadvantage is that you need to write additional code that executes the query. But it gets even worse if the entity has multiple associations and you need to initialize different associations for different use cases. In this case, you need to write a query for every required combination of associations you want to initialize. That 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 codebase. So before you start to write lots of queries, you should think about the number of different fetch join combinations you 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 you 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. Hibernate retrieves the entity and the relation with one query from the database, and you need specific code for every combination of associations. But you often already have lots of use cases specific query code, if you are using the Criteria API. So this might not be a huge issue. If you 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 you need to annotate a named entity graph for each combination of associations that shall be retrieved within one query. You 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 you 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 you 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 using dynamic entity graphs if you need to create a use case specific graph, which you will not reuse. If you 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 you might need a lot of different queries.
- The Criteria API also supports fetch joins and you need specific code for each combination of associations that shall be initialized.
- Named entity graphs are a good solution if you will reuse the defined graph in our code.
- Dynamic entity graphs can be the better solution if you need to define a use case specific graph.