A beginner’s guide to Hibernate fetching strategies


When it comes to working with an ORM tool, everybody acknowledges the importance of database design and Entity-to-Table mapping. These aspects get a lot of attention, while things like fetching strategy might be simply put-off.

In my opinion, the entity fetching strategy shouldn’t ever be separated from the entity mapping design, since it might affect the overall application performance unless properly designed.

Before Hibernate and JPA got so popular, there was a great deal of effort put into designing each query, because you had to explicitly select all the joins you wanted to select from, and all the columns you were interested in. And if that was not enough, the DBA would optimize the slow running queries.

In JPA times, the JPQL or HQL queries are fetching Entities along with some of their associated relationships. This eases development, as it frees us from manually choosing all table fields we are interested in, and sometimes joins or additional queries are automatically generated for serving our needs.

This is a double-edged sword. On one hand, you can deliver features faster, but if your automatically generated SQL queries are not efficient, your overall application performance might suffer significantly.

So what is the entity fetching strategy anyway?

When JPA loads an entity, it also loads all the EAGER or “join fetch” associations too. As long as the persistence context is opened, navigating the LAZY associations results in fetching those as well, through additionally executed queries.

By default, the JPA @ManyToOne and @OneToOne annotations are fetched EAGERly, while the @OneToMany and @ManyToMany relationships are considered LAZY. This is the default strategy, and Hibernate doesn’t magically optimize your object retrieval, it only does what is instructed to do.

While small projects don’t require a thorough entity fetching planning, medium to large applications shouldn’t ever ignore it.

Planning your fetching strategy from the very beginning, and adjusting it all along the development cycle isn’t a “premature optimization”; it’s just a natural part of any ORM design.

The default fetch strategy is the one you define through the JPA mapping, while the manual join fetching is when you use JPQL queries.

The best advice I can give you is to favor the manual fetching strategy (defined in JPQL queries using the fetch operator). While some @ManyToOne or @OneToOne associations make sense to always be fetched eagerly, most of the time, they aren’t needed for every fetching operation.

For children associations, it’s always safer to mark them LAZY and only “join fetch” them when needed, because those can easily generate large SQL result sets, with unneeded joins.

Having most of the associations defined as LAZY requires us to use the “join fetch” JPQL operator and retrieve only the associations we need to fulfill a given request. If you forget to “join fetch” properly, the Persistence Context will run queries on your behalf while you navigate the lazy associations, and that might generate N+1 query problems, or additional SQL queries which might have been retrieved with a simple join in the first place.

Testing time

For a concrete example, let’s start from the following diagram:


The Product entity associations are mapped as:

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "company_id", nullable = false)
private Company company;

@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "product", optional = false)
private WarehouseProductInfo warehouseProductInfo;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "importer_id")
private Importer importer;

@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "product", orphanRemoval = true)
private Set<Image> images = new LinkedHashSet<Image>();

Most of the associations are marked as LAZY because there is no need to fetch all of them every time we load a Product. The warehouse is only needed when displaying the stock information. The Importer is used in certain displays only, and we will fetch it when necessary. The images are lazy since not all views require displaying those images.

Only the company is fetched eagerly because all our views need it, and in our application, a Product always must be considered in the context of a given Company.

It’s a good practice to set the default fetch strategy explicitly (it makes the code more self-descriptive) even if @ManyToOne uses the EAGER fetch option by default.

Use case 1: Loading a product by id

It generates the following SQL:

SELECT product0_.id          AS id1_7_1_,
       product0_.code        AS code2_7_1_,
       product0_.company_id  AS company_4_7_1_,
       product0_.importer_id AS importer5_7_1_,
       product0_.name        AS name3_7_1_,
       company1_.id          AS id1_1_0_,
       company1_.name        AS name2_1_0_
FROM   product product0_
INNER JOIN company company1_ ON product0_.company_id = company1_.id
WHERE  product0_.id = ?

Every time we load through the entity manager the default fetching strategy comes into play, meaning the Company gets fetched along with the Product we are selecting.

Use case 2: Selecting the Product using a JPQL query

The JPQL and Criteria queries might override the default fetch plan.

   "select p " +
   "from Product p " +
   "where p.id = :productId", Product.class)
.setParameter("productId", productId)

The following SQL query is executed:

SELECT product0_.id          AS id1_7_,
       product0_.code        AS code2_7_,
       product0_.company_id  AS company_4_7_,
       product0_.importer_id AS importer5_7_,
       product0_.name        AS name3_7_
FROM   product product0_
WHERE  product0_.id = ?

SELECT company0_.id as id1_6_0_, 
       company0_.name as name2_6_0_ 
FROM   Company company0_ 
WHERE  company0_.id=?

JPQL queries may override the default fetching strategy. If we don’t explicitly declare what we want to fetch using inner or left join fetch directives, the default select fetch policy is applied. For LAZY associations, all uninitialized proxies are vulnerable to LazyInitializationException, if accessed from within a closed Persistence Context. If the Persistence Context is still open it will generate additional select queries, which might end up in N+1 query issues.

Use case 3: Selecting a list of Products with an explicit join fetch plan

This time, we’ll load a Product list along with its associated warehouse and importer relationships.

   "select p " +
   "from Product p " +
   "inner join fetch p.warehouseProductInfo " +
   "inner join fetch p.importer", Product.class)

This generate the following SQL:

SELECT product0_.id          AS id1_7_0_,
       warehousep1_.id       AS id1_11_1_,
       importer2_.id         AS id1_3_2_,
       product0_.code        AS code2_7_0_,
       product0_.company_id  AS company_4_7_0_,
       product0_.importer_id AS importer5_7_0_,
       product0_.name        AS name3_7_0_,
       warehousep1_.quantity AS quantity2_11_1_,
       importer2_.name       AS name2_3_2_
FROM   product product0_
INNER JOIN warehouseproductinfo warehousep1_ ON product0_.id = warehousep1_.id
INNER JOIN importer importer2_ ON product0_.importer_id = importer2_.id

SELECT company0_.id AS id1_6_0_ ,
       company0_.name AS name2_6_0_
FROM   Company company0_
WHERE  company0_.id = 1

Here you can see that the JPQL explicit fetch strategy overrides the default LAZY strategy.
The EAGER association cannot be overridden, and that’s the reason for the second sub-select.

Use case 4: Selecting a list of Images while explicitly join fetching the Product

The default fetch strategy is overridden by the JPQL query. To fetch the parent entity we have to explicitly fetch it:

   "select i " +
   "from Image i " +
   "inner join fetch i.product p " +
   "where p.id = :productId", Image.class)
.setParameter("productId", productId)

This generates the following SQL:

SELECT image0_.id            AS id1_2_0_,
       product1_.id          AS id1_7_1_,
       image0_.index         AS index2_2_0_,
       image0_.name          AS name3_2_0_,
       image0_.product_id    AS product_4_2_0_,
       product1_.code        AS code2_7_1_,
       product1_.company_id  AS company_4_7_1_,
       product1_.importer_id AS importer5_7_1_,
       product1_.name        AS name3_7_1_
FROM   image image0_
INNER JOIN product product1_ ON image0_.product_id = product1_.id
WHERE  product1_.id = ?

If you enjoyed this article, I bet you are going to love my book as well.


There is one more thing I have to add, and it’s about the @oneToOne relationship for warehouseProductInfo. For optional @OnetoOne associations, the LAZY attribute is ignored, since Hibernate must know if it has to populates your Entity with null or with a proxy. In our example, it makes sense to make it mandatory, since every product is located in a warehouse anyway. In other cases, you can simply make the association unidirectional, and keep only the part controlling the link (the one where the foreign key resides).

Code available on GitHub.

Enter your email address to follow this blog and receive notifications of new posts by email.


14 thoughts on “A beginner’s guide to Hibernate fetching strategies

  1. Hi Vlad,

    I have tried the above Usecase 3 with Hibernate 4.3.11 and JPA 2.1. My em.createQuery is as follows “em.createQuery(“select p from Product p inner join fetch p.importer”, Product.class).getResultList();”.

    The “join fetch” with Company, the EAGER association, is ignored as you mentioned, but it immediately fires a SELECT query to load the company (EAGER association). The sql output below:

    Hibernate: select product0_.id as id1_2_0_, importer1_.id as id1_1_1_, product0_.company_id as company_2_2_0_, product0_.importer_id as importer3_2_0_ from JIRAProduct product0_ inner join JIRAImporter importer1_ on product0_.importer_id=importer1_.id
    Hibernate: select company0_.id as id1_0_0_ from JIRACompany company0_ where company0_.id=?

    So I am not able to understand the statement “you can see that the JPQL explicit fetch strategy overrides the default strategy.” Because, even in use case 3, the HQL query finally loads company association in a subsequent SELECT, which is same as that of use case 2, unless I misinterpreted the statement.

  2. Vlad, thanks for all the hard work. What is the best way to tell jpa/hibernate to use an inner join in user case 2, rather than the extra select fetch to get the company info?

    1. That’s simple. Just use the JOIN FETCH directive:

      "select p " +
      "from Product p join fetch p.company " +
      "where p.id = :productId", Product.class)
      .setParameter("productId", productId)

      1. So I guess you are only using JOIN FETCH (which will use an inner join in SQL?) to prevent jpa/hibernate from issuing a separate SELECT when we are only interested in “one product” with a given id? Just to clarify, is it possible to use jpaql to query for a list of products matching specific ids (say just two ids), without having jpa/hibernate (entity with @manytoone relationships) issue a separate select fetch for company (say in situations where company info does not need to be rendered) without using the JOIN FETCH strategy? I hope this makes sense. Thanks for taking the time to reply. Greatly appreciated!!! Gracias!!!

      2. In the blog it says
        “The EAGER association cannot be overridden, and that’s the reason for the second sub-select.”
        Howcome, above the “join fetch p.company” is allowed ?

  3. Hi Vlad,

    Many thanks for this great work. with case 3 what if I want to have something like :

       "select new Product(p.importer, p.warehouseProductInfo ) " +
       "from Product p " +
       "inner join fetch p.warehouseProductInfo " +
       "inner join fetch p.importer", Product.class)

    this causes “query specified join fetching, but the owner of the fetched association was not present in the select list”. but what if I want this to join fetch only specific ManyToOne properties without the others.

    1. The join fetch direct translated to a select * from the joined association. However, that’s conflicting with your DTO projection, so you need to drop the fetch keyword like this:

         "select new Product(p.importer, p.warehouseProductInfo ) " +
         "from Product p " +
         "inner join p.warehouseProductInfo " +
         "inner join p.importer", Product.class)

      This way, it should work just fine.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s