Hibernate Tip: Create an EntityGraph with multiple SubGraphs

By Thorben Janssen

Criteria API, Query

Hibernate Tips is a series of posts in which I describe a quick and easy solution for common Hibernate questions. If you have a question for a future Hibernate Tip, please post a comment below.

Question:

On my tutorial about JPA’s EntityGraphs, Bipin Shrestha asked the following question:

“Can you show me an example of how to use subgraphs inside subgraphs?”

Sure, I’ve updated the tutorial to include an example for it, but I also thought that it’s an interesting topic for a Hibernate Tip.

Solution:

An EntityGraph provides an excellent way to avoid n+1 select issues by initializing the required, lazily fetched associations. The definition of the graph is independent of your query and defines which associations Hibernate shall initialize before returning your query results.

Let’s take a look at an example.

Class diagram: Author, Book, Publisher, and Employee entity

One or more Authors wrote each Book and it was published by a Publisher. Each Publisher employs one or more Employees.

Let’s create a JPQL query that returns an Author entity with initialized associations to the Book, Publisher, and Employee entities.

As you can see in the following code snippet, the query itself is simple.

TypedQuery<Author> q = em.createQuery("SELECT a FROM Author a WHERE a.id = 100", Author.class);
Author a = q.getSingleResult();

What’s more interesting is the EntityGraph that tells Hibernate to fetch all the associations.

Defining the EntityGraph

Watch it on YouTube https://youtu.be/jJl71UsChdM

There are multiple ways to define an EntityGraph. The JPA specification provides a set of annotations, which I will use in this article, and an API. In addition to that, Hibernate can also parse a String into an EntityGraph.

Ok, let’s dive into the annotation-based example.

You can use the @NamedEntityGraph, @NamedAttributeNode, and @NamedSubgraph annotations to specify a graph of entities that Hibernate shall fetch from the database.

@Entity
@NamedEntityGraph(
	name = "graph.AuthorBooksPublisherEmployee", 
	attributeNodes = @NamedAttributeNode(value = "books", subgraph = "subgraph.book"), 
	subgraphs = {
		@NamedSubgraph(name = "subgraph.book", 
					   attributeNodes = @NamedAttributeNode(value = "publisher", subgraph = "subgraph.publisher")),
		@NamedSubgraph(name = "subgraph.publisher", 
					   attributeNodes = @NamedAttributeNode(value = "employees")) })
public class Author { ... }
Watch it on YouTube https://youtu.be/Zs8Dse_Mpyk

The @NamedEntityGraph annotation defines the root of the graph. It specifies the fetching behavior for the entity returned by a query. In this example, it does that for the Author entity.

For each @NamedEntityGraph, you can provide an array of @NamedAttributeNode annotations. Each of them references an entity attribute which Hibernate shall fetch from the database. I use it here to fetch the books attribute of the Author entity.

If your @NamedAttributeNode references an associated entity, you also might want to define the fetching behavior for that entity. You can do that by referencing a subgraph.

This subgraph gets defined by a @NamedSubgraph annotation. As you can see in the previous code snippet, that annotation is very similar to a @NamedEntityGraph annotation. It allows you to provide an array of @NamedAttributeNode annotations which can reference additional subgraphs. I use that annotation to create a subgraph for the Book and the Publisher entity.

Avoid Huge EntityGraphs

This approach enables you to create very deep graphs of entities that Hibernate fetches from the database. But you should be very careful about using entity graphs like that.

Each reference entity requires Hibernate to join another database table and to select all database columns mapped by the entity. That can create a huge result set and slow down your database query.

As a rule of thumb, your entity graph should only reference 1-2 other entities. If you need to fetch a bigger graph, you should double-check your query and talk with your DBA about its performance-impact.

Using the EntityGraph

I explained the different ways to use an EntityGraph in more details in my article JPA Entity Graphs: How to Define and Use a NamedEntityGraph. So, I keep this explanation short.

If you want to use the defined graph with your query, you need to instantiate an EntityGraph object. You can do that by calling the createEntityGraph method on your EntityManager with the name of the graph.

In the next step, you can add a hint to your query. The hint adds your graph as a javax.persistence.fetchgraph or javax.persistence.loadgraph.

EntityGraph<?> graph = em.createEntityGraph("graph.AuthorBooksPublisherEmployee");
TypedQuery<Author> q = em.createQuery("SELECT a FROM Author a WHERE a.id = 100", Author.class);
q.setHint("javax.persistence.fetchgraph", graph);
Author a = q.getSingleResult();

When you activate the logging of SQL statements and execute the query, you can see that Hibernate generated a query that joins the Author, BookAuthor, Book, Publisher, and Employee tables and selects the columns mapped by all 4 entities.

06:15:56,900 DEBUG [org.hibernate.SQL] - 
    select
        author0_.id as id1_0_0_,
        book2_.id as id1_1_1_,
        publisher3_.id as id1_4_2_,
        employees4_.id as id1_3_3_,
        author0_.firstName as firstNam2_0_0_,
        author0_.lastName as lastName3_0_0_,
        author0_.version as version4_0_0_,
        book2_.publisherid as publishe5_1_1_,
        book2_.publishingDate as publishi2_1_1_,
        book2_.title as title3_1_1_,
        book2_.version as version4_1_1_,
        books1_.authorId as authorId2_2_0__,
        books1_.bookId as bookId1_2_0__,
        publisher3_.name as name2_4_2_,
        publisher3_.version as version3_4_2_,
        employees4_.firstName as firstNam2_3_3_,
        employees4_.lastName as lastName3_3_3_,
        employees4_.publisher_id as publishe5_3_3_,
        employees4_.version as version4_3_3_,
        employees4_.publisher_id as publishe5_3_1__,
        employees4_.id as id1_3_1__ 
    from
        Author author0_ 
    left outer join
        BookAuthor books1_ 
            on author0_.id=books1_.authorId 
    left outer join
        Book book2_ 
            on books1_.bookId=book2_.id 
    left outer join
        Publisher publisher3_ 
            on book2_.publisherid=publisher3_.id 
    left outer join
        Employee employees4_ 
            on publisher3_.id=employees4_.publisher_id 
    where
        author0_.id=100

This log message clearly shows the downside of huge EntityGraphs.

I added the defined graph to a very simple query. But due to the complex graph definition, Hibernate had to generate a complex SQL query that joins 5 tables and selects 21 columns.

So, better be careful with your graph definitions and make sure to always check the generated SQL statement.

Learn more:

If you want to learn more about EntityGraphs and Hibernate’s fetching behavior, you should also read the following articles:

 

Hibernate Tips Book







Get more recipes like this one in my new book Hibernate Tips: More than 70 solutions to common Hibernate problems.

It gives you more than 70 ready-to-use recipes for topics like basic and advanced mappings, logging, Java 8 support, caching, and statically and dynamically defined queries.

Get it now!



Tags

Criteria API, Query


About the author

Thorben is an independent consultant, international speaker, and trainer specialized in solving Java persistence problems with JPA and Hibernate.
He is also the author of Amazon’s bestselling book Hibernate Tips - More than 70 solutions to common Hibernate problems.

Books and Courses

Coaching and Consulting

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.

  1. Hi, i would like to know more about collection mappings in JPA/Hibernate, more specifically the problem that we found when we have more than one List mapping in a class. Thanks!

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}