| |

5 ways to initialize lazy associations and when to use them


Take your skills to the next level!

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.


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.

Order order = this.em.find(Order.class, orderId);
order.getItems().size();

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.

Query q = this.em.createQuery("SELECT o FROM Order o JOIN FETCH o.items i WHERE o.id = :id");
q.setParameter("id", orderId);
newOrder = (Order) q.getSingleResult();

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.

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery q = cb.createQuery(Order.class);
Root o = q.from(Order.class);
o.fetch("items", JoinType.INNER);
q.select(o);
q.where(cb.equal(o.get("id"), orderId));

Order order = (Order)this.em.createQuery(q).getSingleResult();

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.

@Entity
@NamedEntityGraph(name = "graph.Order.items", 
      attributeNodes = @NamedAttributeNode("items"))
public class Order implements Serializable { ... }

The named entity graph can then be used by the find method of the EntityManager.

EntityGraph graph = this.em.getEntityGraph("graph.Order.items");
  
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);
  
Order order = this.em.find(Order.class, orderId, hints);

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.

EntityGraph graph = this.em.createEntityGraph(Order.class);
Subgraph itemGraph = graph.addSubgraph("items");
    
Map hints = new HashMap();
hints.put("javax.persistence.loadgraph", graph);
  
Order order = this.em.find(Order.class, orderId, hints);

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.

Conclusion

You had a look at 5 different ways to initialize lazy associations. And as you have seen, each of them has its advantages and disadvantages. So what to remember from this article?

  • 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.

13 Comments

  1. Hi. I synchronize my bi-directional relationships in services. If I fetch a lazy relationship by JPQL, then call the .add method on the list, I will incur in N+1 issue, or JPA will be smart enough to not fetch the list again?

    @Transactional
    public Child create(Parent parent Child child) {

    parentService.findParentFetchingChilds(parent.getId);

    child.setParent(parent);
    child.getParent().addChild(child);

    return child;
    }

    1. Avatar photo Thorben Janssen says:

      Hi 4javier,

      Unfortunately, all JPA implementations will fetch the list with all associated entities.

      Regards,
      Thorben

  2. You should mention that multiple “join fetch” in the same query create a cross join and this could have a huge performance impact. I never use more than one.

    1. Avatar photo Thorben Janssen says:

      Hi Christian,

      Multiple JOIN FETCH clauses don’t cause a cross join, but they create a product that can become a performance issue. That’s why I agree with you that you should be careful with the number of JOIN FETCH clauses in your query. If the cardinality of your associations is low, 1-3 JOIN FETCHes can be OK. But if only one of them contains several hundred or thousand records, that one on its own will cause performance issues.

      Regards,
      Thorben

  3. Thanks for the good article! Is it possible to get a collection with entity graphs? Something like
    List list = entityManager.createEntityGraphQuery(“named_entity_graph_name”).getResultList();

    1. Avatar photo Thorben Janssen says:

      No, the name of a graph has to be unique and you need to add it to a query to get any results.

  4. Avatar photo kamboyamilan says:

    Nice articles. Helped in understanding.

  5. Avatar photo Dinesh Kumar says:

    Nice post.
    Thanks.

  6. Avatar photo Sanjeev Dhiman says:

    I just subscribed on your mailing list by reading some of your blogs. Your plain English and to-the-point-talk are wonderful. Short and sweet examples are like icing on the cake.

    Thank you very much for sharing your knowledge with us.

    Btw, did you write any book on any of these topics?

  7. Thorben,
    Thanks a lot for this great article! I will definitely bookmark it!
    Best, Nabi

  8. Avatar photo Frederic Rey says:

    I didn’t know about the entity graghs! really helpful features! thanks for this article

    1. Avatar photo Thorben Janssen says:

      Thanks Frederic!
      Entity Graphs are really helpful to avoid performance issues and my favorite change in JPA 2.1. Another great feature introduced in JPA 2.1 are AttributeConverter: //thorben-janssen.com/jpa-21-how-to-implement-type-converter/

Comments are closed.