Overriding FetchType.EAGER with fetchgraph

Are you struggling with performance issues in your Spring, Jakarta EE, or Java EE application?

What if there were a tool that could automatically detect what caused performance issues in your JPA and Hibernate data access layer?

Wouldn’t it be awesome to have such a tool to watch your application and prevent performance issues during development, long before they affect production systems?

Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, Micronaut, or Play Framework.

So, rather than fixing performance issues in your production system on a Saturday night, you are better off using Hypersistence Optimizer to help you prevent those issues so that you can spend your time on the things that you love!

Introduction

In this article, we are going to see how you can override the FetchType.EAGER strategy using the fetchgraph query hint.

While this feature has been available in the JPA specification since version 2.1, Hibernate has only supported this feature since version 5.5.

JPA FetchType.EAGER and FetchType.LAZY strategies

The fetching strategy can be specified either at the entity mapping level or when building a query.

At the entity mapping level, the fetch attribute of the @ManyToOne, @OneToOne, @OneToMany, @ManyToMany, or @ElementCollection can use either the FetchType.LAZY or FetchType.EAGER value.

The @ManyToOne and @OneToOne associations use the FetchType.EAGER strategy by default while the @OneToMany, @ManyToMany, and @ElementCollection associations use the FetchType.LAZY strategy.

As I explained in this article, it’s much more efficient if all associations are set to using the FetchType.LAZY strategy ate entity mapping level since we can always override it at query time using a JOIN FETCH directive.

However, while you can easily eagerly fetch an association that was set to FetchType.LAZY at the entity mapping level, the reverse was not possible until Hibernate implemented the fetchgraph property as indicated by the JPA specification.

The default behavior of FetchType.EAGER

Let’s assume we have the following Post and PostComment entities that are associated via the post reference in the PostComment child entity:

Post and PostComment entities for fetchgraph

The parent Post entity is mapped as follows:

@Entity
@Table(name = "post")
public class Post {
 
    @Id
    private Long id;
 
    private String title;
}

And the PostComment child entity used the default @ManyToOne association mapping:

@Entity
@Table(name = "post_comment")
public class PostComment {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String review;
 
    @ManyToOne
    private Post post;
}

Because the @ManyToOne association uses the FetchType.EAGER strategy by default, every time we load the PostComment entity using the find method we can see that the post association is fetched as well:

PostComment comment = doInJPA(entityManager -> {
    return entityManager.find(PostComment.class, 1L);
});

assertTrue(Hibernate.isInitialized(comment.getPost()));
assertEquals(
    "High-Performance Java Persistence",
    comment.getPost().getTitle()
);

When checking the query log, we can see that Hibernate executed the following SQL query:

SELECT pc.id, p.id, p.title, pc.review 
FROM post_comment pc 
LEFT JOIN post p ON p.id = pc.post_id 
WHERE pc.id = 1

Because of the default FetchType.EAGER strategy, Hibernate uses the LEFT JOIN to fetch the associated post table record.

However, if we don’t need the post association, then fetching it along with the PostComment is just a useless overhead.

If the find method uses a LEFT JOIN to fetch the associations that use the FetchType.EAGER strategy, an entity query will use a secondary query instead.

So, when fetching the PostComment entity using the following JPQL query, we can see that the post entity is still fetched by default even if we don’t provide an explicit JOIN FETCH directive:

PostComment comment = doInJPA(entityManager -> {
    return entityManager.createQuery("""
        select pc
        from PostComment pc
        where pc.id = :id
        """, PostComment.class)
    .setParameter("id", 1L)
    .getSingleResult();
});

assertTrue(Hibernate.isInitialized(comment.getPost()));
assertEquals(
    "High-Performance Java Persistence",
    comment.getPost().getTitle()
);

When running the above JPQL query, Hibernate generates the following SQL queries:

SELECT pc.id, pc.post_id, pc.review 
FROM post_comment pc 
WHERE pc.id = 1

SELECT p.id, p.title 
FROM post p 
WHERE p.id = 1

The post entity is fetched using a secondary query because of the FetchType.EAGER strategy used by the @ManyToOne association.

Overriding FetchType.EAGER with fetchgraph

If you have a FetchType.EAGER association that you want to avoid fetching at query time, then you need to use the fetchgraph property.

In the JPA 2.1 and 2.2, the property was called javax.persistence.fetchgraph, and from JPA 3.0 is called jakarta.persistence.fetchgraph.

However, instead of using the hard-coded value in your queries, you are better off using the associated constant provided by the Hibernate SpecHints interface.

If we want to override a FetchType.EAGER association, we have to do two things:

  1. First, we have to create an Entity Graph that does not include the association we want to avoid fetching.
  2. Afterward, we need to provide the newly created Entity Graph using the fetchgraph property to the fetching method.

So, if we create an empty PostComment Entity Graph and set it via the fetchgraph property when fetching the PostComment entity via the find method:

PostComment comment = doInJPA(entityManager -> {
    return entityManager.find(PostComment.class, 1L,
        Map.of(
            SpecHints.HINT_SPEC_FETCH_GRAPH,
            entityManager.createEntityGraph(PostComment.class)
        )
    );
});
 
assertFalse(Hibernate.isInitialized(comment.getPost()));
try {
    comment.getPost().getTitle();
 
    fail("Should throw LazyInitializationException");
} catch(LazyInitializationException expected) {}

We can see that the post association is no longer fetched eagerly and a Proxy will be used instead.

When checking the executed SQL query, we can see that there’s no LEFT JOIN to the post table:

SELECT pc.id, pc.post_id, pc.review 
FROM post_comment pc 
WHERE pc.id = 1

The post_id column value will be used to set the identifier value on a Post proxy object that looks as follows:

comment = {PostComment@6445} 
 id = {Long@6456} 1
 review = "The first part is about JDBC"
 post = {Post$HibernateProxy$zLsYXLhX@6458}
  $$_hibernate_interceptor = {ByteBuddyInterceptor@6461} 
   interfaces = {Class[1]@6462} 
   persistentClass = {Class@3334} "class com.vladmihalcea.hpjp.hibernate.fetching.Post"
   ...
   entityName = "com.vladmihalcea.hpjp.hibernate.fetching.Post"
   id = {Long@6456} 1
   ...
   initialized = false
   readOnly = false
   unwrap = false
   session = null
   readOnlyBeforeAttachedToSession = null
   sessionFactoryUuid = "171c4da9-9e3f-42cf-a5db-2319f2f3e63c"
   sessionFactoryName = null
   allowLoadOutsideTransaction = false
  id = null
  title = null

The fetchgraph is not limited to find method calls. If we are executing a JPQL, Criteria API, or native SQL query, then we can set the fetchgraph via the setHint method:

PostComment comment = doInJPA(entityManager -> {
    return entityManager.createQuery("""
        select pc
        from PostComment pc
        where pc.id = :id
        """, PostComment.class)
    .setHint(
        SpecHints.HINT_SPEC_FETCH_GRAPH,
        entityManager.createEntityGraph(PostComment.class)
    )
    .setParameter("id", 1L)
    .getSingleResult();
});

assertFalse(Hibernate.isInitialized(comment.getPost()));
try {
    comment.getPost().getTitle();
 
    fail("Should throw LazyInitializationException");
} catch(LazyInitializationException expected) {}

The difference between fetchgraph and loadgraph

If you take a look at the JPA specification, you will see that there is also a loadgraph option. However, this property is not suitable for overriding the FetchType.EAGER strategy.

The loadgraph is used to specify which FetchType.LAZY associations are to be fetched eagerly at query time. However, it leaves all the FetchType.EAGER associations to be fetched either via a LEFT JOIN or a secondary query.

So, in our case, if we use the loadgraph property, we can see that the post entity will be fetched eagerly:

PostComment comment = doInJPA(entityManager -> {
    return entityManager.find(PostComment.class, 1L,
        Map.of(
            SpecHints.HINT_SPEC_LOAD_GRAPH,
            entityManager.createEntityGraph(PostComment.class)
        )
    );
});

assertTrue(Hibernate.isInitialized(comment.getPost()));
assertEquals(
    "High-Performance Java Persistence",
    comment.getPost().getTitle()
);

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

Starting with Hibernate 5.5, the FetchType.EAGER fetching strategy can be overridden at query time via the fetchgraph property.

While you are still better off to avoid mapping your entity association using the FetchType.EAGER strategy, if you are dealing with a legacy project, then you can use the fetchgraph property to avoid fetching the associations that are not required by a given business use case, therefore improving transaction response time.

Transactions and Concurrency Control eBook

2 Comments on “Overriding FetchType.EAGER with fetchgraph

  1. I read nearly 20 of your articles in 3 days, I learned a lot. Thank you very much for these wonderful articles.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.