Hi,
I’m Thorben Janssen from thoughts-on-java.org. Lazy loading of associated entities is a well-established
best practice in JPA. Its main goal is to retrieve only the requested entities from
the database and to load the related entities only if needed. That is a great approach if
you only need the entities you selected in your query. But it creates additional work
and can be the cause of performance problems if you also need some of the associated entities.
In this video, I want to show you 5 different ways to trigger the initialization of lazy
associations and their specific advantages and disadvantages.
Let’s start with the most obvious and unfortunately also the most inefficient approach. You don’t
worry about the initialization and just use the mapped association. You can see an example of this approach here. Each order has multiple items, and I modeled
that as a lazy one-to-many association between the Order and the Item entity.
The easiest way to initialize this association is to fetch an Order entity and use the getItems()
method. This code works perfectly fine, is easy to
read and often used. So what is the problem with it?
You probably already know it. This code performs an additional query to fetch the associated
entities. That doesn’t sound like a real issue but let’s calculate the number of
queries in a more real world-ish scenario. Let’s say we have an entity with 5 associations
which we need to initialize. So we will get 1 query to fetch the entity, plus 5 queries to load
the associated entites. That are 6 queries in total.
That still doesn’t seem like a huge issue. But you often have to select more than 1 entity.
Let’s say you select 100 entities. Hibernate then has to perform 500 additional 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. A better option to initialize lazy associations is to use a JPQL query with a JOIN FETCH clause.
It tells the EntityManager to load the selected entity and the association within the same
query. That avoids all the additional queries we had in the previous approach. Here you can see an example of such a JPQL query. You define the JOIN FETCH clause in
the same way as a normal JOIN clause. When I run this test case, you will see that Hibernate
executed only 1 query. Here it is, Hibernate fetched the Order entity
and the associated Items with 1 query. The advantages and disadvantages of this approach
are obvious: The main advantage is that Hibernate fetches
all entities 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 associations and we need to initialize different relationships for different use
cases. In this case we need to write a query for every required combination of associations
we want to initialize. That can become quite a mess.
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. If not, you should have a look at option 4
and 5. But before I explain the other options, I
want to quickly show you that you can also define a JOIN FETCH clause with the Criteria
API. You can see a simple CriteriaQuery here. It
selects all Order entities from the database, and I call the fetch method on the Root interface
to create a JOIN FETCH clause so that Hibernate also loads the associated entities.
The advantages and disadvantages are the same as for the JPQL query with a fetch join. Hibernate
loads the entity and the associated entities with one query, and you need specific code
for every combination of associations. But you often already have lots of use case specific
query code, if you are using the Criteria API. So this might not be a huge issue. If
you use the Criteria API to build the query, this is a good approach to fetch the associated
entities efficiently. You can see the generated SQL query here.
It’s the same as in the previous example, and it gets the Order entity together with
the associated Item entities. Named entity graphs were introduced in JPA
2.1. You can use them to define a graph of entities that Hibernate shall load from the
database. The definition of an entity graph is done via annotations and is independent
of the query. I will just show you a quick example of how
you can define such a graph. I wrote a blog post that explains NamedEntityGraphs in more
details. You should have at it if you’re not familiar with this feature. I provide
a link to it in the video description. Here you can see the definition of a @NamedEntityGraph
which tells Hibernate to initialize the entity attribute with the name “items”. That
is the one that models the lazy one-to-many association between the Order and the Item
entity. So, this @NamedEntityGraph does the same as I did with the JOIN FETCH statements
in the JPQL and CriteriaQuery. But this one is independent of the query,
and you can also use it with the EntityManager.find method. I do that here. I first call the getEntityGraph
method with the name of the @NamedEntityGraph to instantiate an EntityGraph. Then I create
a Map with query hints and add my graph as a "javax.persistence.fetchgraph“. And in
the final step, I provide the Map with query hints as a parameter to the EntityManager.find
method. That’s it. Let’s run this test and see
if it works. As you can see here, the @NamedEntityGraph
worked as expected. Hibernate performed only 1 query to fetch the Order entity with its
associated Item entities. This is basically an improved version of the first approach.
The only disadvantage is that you need to annotate a named entity graph for each combination
of associations that Hibernate shall retrieve 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 only a great solution, if you don’t need too many of them and if
you reuse them for different use cases. Otherwise, the code will become hard to maintain. The dynamic entity graph is similar to the named entity graph, and I also explained it in more
details in one of the older blog posts. The only difference is that you use a Java API to define
the entity graph. That provides you more flexibility if you need to adapt your graph based on user
input. These 2 lines define the same graph as the
annotations in the previous example. It tells Hibernate to initialize the items attribute
of the Order entity. And you should be familiar with the rest of
this test case. It’s the same as in the previous example. I create a Map of query
hints, add the graph and provide the map to the EntityManager.find method. Hibernate handled the EntityGraph in the same
way as in the previous example and fetched the Order entity together with the associated
Item entities. So, when should you use a dynamic instead
of a named entity graph? 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 your Java code. That provides more flexibility and avoids
entities with dozens of annotations. On the other hand, the dynamic entity graph requires
more code and an additional method to be reusable. I recommend using dynamic entity graphs if
you need to define 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. OK, that’s it for today. I prepared a cheat sheet with the most important facts of this
video. You can find it in the Thoughts on Java Library.
If you’re not already a member, you can signup for free. That also gives you free access
to other cheat sheets, ebooks, and a video course. You can find a link to it in the video
description. But before you go there, please give this
video a thumbs up and subscribe below so that you don’t miss any future videos.
Bye!