|

FetchType: Lazy/Eager loading for Hibernate & JPA


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.


Choosing the right FetchType is one of the most important decisions when defining your entity mapping. It specifies when your JPA implementation, e.g., Hibernate, fetches associated entities from the database. You can choose between EAGER and LAZY loading. The first one fetches an association immediately, and the other only when you use it. I explain both options in this article.

The main challenge when choosing the right FetchType is to ensure you fetch your entities as efficiently as possible and avoid fetching anything you don’t need. But that’s more complex than it might seem. You statically specify the FetchType in your entity’s mapping definition, and Hibernate uses it every time it fetches an entity. That makes it challenging to choose a FetchType that’s a good match for all your use cases and why you should combine it with query-specific fetching. I will provide more information about that at the end of this article.

But let’s first dive deeper into the different FetchTypes and their definition.

Default FetchTypes and how to change it

The JPA specification defines a default FetchType for all association types. It gets used whenever you don’t specify a FetchType.

This default depends on the cardinality of the association. All to-one associations use FetchType.EAGER and all to-many associations FetchType.LAZY.

You can override the default by setting the fetch attribute of the @OneToMany, @ManyToOne, @ManyToMany, and @OneToOne annotation.

@Entity
@Table(name = "purchaseOrder")
public class Order implements Serializable {
  
  @OneToMany(mappedBy = "order", fetch = FetchType.EAGER)
  private Set<OrderItem> items = new HashSet<OrderItem>();
  
  ...
  
}

OK, let’s take a more detailed look at the different FetchTypes.

FetchType.EAGER – Fetch it so you’ll have it when you need it

FetchType.EAGER tells Hibernate to get the associated entities when selecting the root entity. As I explained earlier, this is the default for to-one associations. You can see its effect in the following code snippets.

I use the default FetchType.EAGER on the @ManyToOne association between the OrderItem and Product entity.

@Entity
public class OrderItem implements Serializable
{
  
   @ManyToOne
   private Product product;
   
   ...
}

When I now fetch an OrderItem entity from the database, Hibernate will also get the associated Product entity.

OrderItem orderItem = em.find(OrderItem.class, 1L);
log.info("Fetched OrderItem: "+orderItem);
Assert.assertNotNull(orderItem.getProduct());

As you can see in the log output, Hibernate executes a query that fetches both entities.

05:01:24,504 DEBUG SQL:92 - select orderitem0_.id as id1_0_0_, orderitem0_.order_id as order_id4_0_0_, orderitem0_.product_id as product_5_0_0_, orderitem0_.quantity as quantity2_0_0_, orderitem0_.version as version3_0_0_, order1_.id as id1_2_1_, order1_.orderNumber as orderNum2_2_1_, order1_.version as version3_2_1_, product2_.id as id1_1_2_, product2_.name as name2_1_2_, product2_.price as price3_1_2_, product2_.version as version4_1_2_ from OrderItem orderitem0_ left outer join purchaseOrder order1_ on orderitem0_.order_id=order1_.id left outer join Product product2_ on orderitem0_.product_id=product2_.id where orderitem0_.id=?
05:01:24,557  INFO FetchTypes:77 - Fetched OrderItem: OrderItem , quantity: 100

This might seem to be an efficient approach. But keep in mind that Hibernate will ALWAYS fetch the Product entity when getting the OrderItem.

That’s even the case if you don’t use the Product entity in your business code. If the associated entity isn’t too big and Hibernate only fetches a to-one association, this isn’t optimal, but also often not a huge issue. That quickly changes if you use FetchType.EAGER on multiple associations or a huge to-many association. Hibernate then has to fetch tens or even hundreds of additional entities, which creates a significant overhead.

FetchType.LAZY – Fetch it when you need it

FetchType.LAZY tells Hibernate only to fetch the associated entities from the database when you use the association for the 1st time. This is a good idea in general because there’s no reason to select entities you don’t use in your business code. You can see an example of a lazily fetched association in the following code snippets.

The @OneToMany association between the Order and the OrderItem entities uses FetchType.LAZY. It’s the default for to-many associations.

@Entity
@Table(name = "purchaseOrder")
public class Order implements Serializable {
  
  @OneToMany(mappedBy = "order")
  private Set<OrderItem> items = new HashSet<OrderItem>();
  
  ...
  
}

The used FetchType does not influence the business code. You can call the getOrderItems() method just as any other getter method.

Order newOrder = em.find(Order.class, 1L);
log.info("Fetched Order: "+newOrder);
Assert.assertEquals(2, newOrder.getItems().size());

Hibernate handles the lazy initialization transparently and fetches the OrderItem entities when you call the getOrderItems() method for the first time.

05:03:01,504 DEBUG SQL:92 - select order0_.id as id1_2_0_, order0_.orderNumber as orderNum2_2_0_, order0_.version as version3_2_0_ from purchaseOrder order0_ where order0_.id=?
05:03:01,545  INFO FetchTypes:45 - Fetched Order: Order orderNumber: order1
05:03:01,549 DEBUG SQL:92 - select items0_.order_id as order_id4_0_0_, items0_.id as id1_0_0_, items0_.id as id1_0_1_, items0_.order_id as order_id4_0_1_, items0_.product_id as product_5_0_1_, items0_.quantity as quantity2_0_1_, items0_.version as version3_0_1_, product1_.id as id1_1_2_, product1_.name as name2_1_2_, product1_.price as price3_1_2_, product1_.version as version4_1_2_ from OrderItem items0_ left outer join Product product1_ on items0_.product_id=product1_.id where items0_.order_id=?

Handling lazy associations this way is perfectly fine if you work on a single Order entity or a small list of entities. However, it becomes a performance problem when you do it on a large list of entities. As you can see in the following log messages, Hibernate has to perform an additional SQL statement for each Order entity to fetch its OrderItems.

05:03:40,936 DEBUG ConcurrentStatisticsImpl:411 - HHH000117: HQL: SELECT o FROM Order o, time: 41ms, rows: 3
05:03:40,939  INFO FetchTypes:60 - Fetched all Orders
05:03:40,942 DEBUG SQL:92 - select items0_.order_id as order_id4_0_0_, items0_.id as id1_0_0_, items0_.id as id1_0_1_, items0_.order_id as order_id4_0_1_, items0_.product_id as product_5_0_1_, items0_.quantity as quantity2_0_1_, items0_.version as version3_0_1_, product1_.id as id1_1_2_, product1_.name as name2_1_2_, product1_.price as price3_1_2_, product1_.version as version4_1_2_ from OrderItem items0_ left outer join Product product1_ on items0_.product_id=product1_.id where items0_.order_id=?
05:03:40,957 DEBUG SQL:92 - select items0_.order_id as order_id4_0_0_, items0_.id as id1_0_0_, items0_.id as id1_0_1_, items0_.order_id as order_id4_0_1_, items0_.product_id as product_5_0_1_, items0_.quantity as quantity2_0_1_, items0_.version as version3_0_1_, product1_.id as id1_1_2_, product1_.name as name2_1_2_, product1_.price as price3_1_2_, product1_.version as version4_1_2_ from OrderItem items0_ left outer join Product product1_ on items0_.product_id=product1_.id where items0_.order_id=?
05:03:40,959 DEBUG SQL:92 - select items0_.order_id as order_id4_0_0_, items0_.id as id1_0_0_, items0_.id as id1_0_1_, items0_.order_id as order_id4_0_1_, items0_.product_id as product_5_0_1_, items0_.quantity as quantity2_0_1_, items0_.version as version3_0_1_, product1_.id as id1_1_2_, product1_.name as name2_1_2_, product1_.price as price3_1_2_, product1_.version as version4_1_2_ from OrderItem items0_ left outer join Product product1_ on items0_.product_id=product1_.id where items0_.order_id=?

This behavior is called the n+1 select issue. It’s the most common reason for performance problems when using Hibernate.

There are a few good and 1 wrong way to solve this issue.

The wrong way is to use FetchType.EAGER. Instead of fixing the problem, it replaces it with another inefficiency. This would only be a good idea, if every time you fetch an Order entity, you also need the associated OrderItems. But that’s usually not the case.

All good ways to solve the n+1 select issue rely on FetchType.LAZY and query-specific fetching. FetchType.LAZY tells Hibernate only to initialize the association when you use it. And when you know that your business code will require an initialized association, you can use query-specific fetching to read it efficiently. Instead of executing an additional query to initialize each association, query-specific fetching gets an entity with a specified list of associations. So, when you fetch an Order entity, you can tell Hibernate to also fetch the associated OrderItems within the same query. I explain the different options for query-specific fetching in this article.

Summary

Let’s quickly summarize the different FetchTypes.

FetchType.EAGER tells Hibernate to get the associated entities with the initial query. This can look very efficient because it fetches all entities with only one query. But in most cases, it creates a huge overhead because Hibernate fetches these entities even if your business code doesn’t use them.

You can prevent this with FetchType.LAZY. It tells Hibernate to delay the initialization of the association until you access it in your business code.

The drawback of this approach is that Hibernate needs to execute an additional query to initialize each association. This is called the n+1 select issue and is one of the most common reasons for performance issues. You can avoid it by using query-specific fetching. The combination of both of them allows you to fetch the required information for every use case efficiently.

5 Comments

  1. I would like to thank you for this beautiful and descriptive article.

  2. Avatar photo Shahid Jabbar says:

    very well written and explained with example

  3. Pretty! This has been an incredibly wonderful post.
    Thank you for supplying this information.

  4. Great post!

    If I’m not wrong EAGER fetching not necessarily fetches the entities with only one query (like using JOIN). It may fetch them through another query.

    What do you think?

    1. Avatar photo Thorben Janssen says:

      Hi Rafael,

      that depends on the FetchMode you use for the relationship. The default behaviour for to-one relationships seems to be to JOIN the related tables in the query.
      But that’s another huge topic which I might cover in a future post and which I explain in great detail in the Hibernate Performance Tuning Training //thorben-janssen.com/course-hibernate-performance-tuning

      Regards,
      Thorben

Comments are closed.