Hibernate Tip: Create an EntityGraph with multiple SubGraphs
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.
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.
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
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 { ... }
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:
- JPA Entity Graphs: How to Define and Use a NamedEntityGraph
- JPA Entity Graphs: How to Dynamically Define and Use an EntityGraph
- 5 ways to initialize lazy relations and when to use them
- Entity Mappings: Introduction to JPA FetchTypes
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.
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!
Hi,
Do you know this article: How to Choose the Most Efficient Data Type for To-Many Associations – Bag vs. List vs. Set?
In that article, I explain how to avoid the MultipleBagFetchException, which is probably the problem you mentioned, and I compare the different collection types.
Regards,
Thorben