Core Java

Understanding @NamedEntityGraph in Hibernate

Hibernate provides several techniques to optimize entity fetching and reduce performance issues such as the N+1 query problem. One of the most powerful and clean approaches is using @NamedEntityGraph. Entity graphs allow developers to control which associations should be eagerly loaded at runtime without permanently changing the fetch type defined in entity mappings.

1. Introduction to Entity Graphs in Hibernate

In enterprise applications, database performance becomes critical when dealing with complex entity relationships. By default, Hibernate supports:

  • LAZY fetching — loads related entities only when needed
  • EAGER fetching — loads related entities immediately

While both approaches are useful, EAGER loading can fetch unnecessary data, LAZY loading may trigger multiple SQL queries, and both can contribute to the N+1 select problem. @NamedEntityGraph solves this problem by allowing developers to dynamically define which associations should be fetched in a single query. This improves performance, keeps repository code cleaner, reduces query duplication, and enables flexible fetching strategies.

2. Understanding @NamedEntityGraph

@NamedEntityGraph is a JPA feature supported by Hibernate that allows developers to define reusable fetch plans for entities. It specifies:

  • Which attributes should be eagerly fetched
  • Which relationships should be joined
  • How nested associations should be loaded

2.1 Defining a Basic Entity Graph

The following example demonstrates how to define a reusable entity graph using the @NamedEntityGraph annotation in Hibernate.

@NamedEntityGraph(
    name = "graph-name",
    attributeNodes = {
        @NamedAttributeNode("associationName")
    }
)

This approach allows developers to separate fetch strategies from entity mappings, making the application more flexible and easier to optimize for different use cases. The graph can later be applied dynamically while executing queries using EntityManager, Session, JPQL queries, or repository methods.

3. Complete Hibernate Example Using @NamedEntityGraph

3.1 Adding Maven Dependencies

<dependencies>

    <dependency>
        <groupId>org.hibernate.orm</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>6.4.4.Final</version>
    </dependency>

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>jakarta.persistence</groupId>
        <artifactId>jakarta.persistence-api</artifactId>
        <version>3.1.0</version>
    </dependency>

</dependencies>

The hibernate-core dependency provides the main Hibernate ORM framework required for entity mapping, session management, and database operations, while the h2 dependency adds an in-memory database that is commonly used for development and testing purposes without requiring external database installation. The jakarta.persistence-api dependency provides the standard JPA annotations and interfaces such as @Entity, @Id, and EntityGraph, allowing the application to use Hibernate as the JPA implementation.

3.2 Configuring hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?>

<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

    <session-factory>

        <property name="hibernate.connection.driver_class">
            org.h2.Driver
        </property>

        <property name="hibernate.connection.url">
            jdbc:h2:mem:testdb
        </property>

        <property name="hibernate.connection.username">
            sa
        </property>

        <property name="hibernate.connection.password">
        </property>

        <property name="hibernate.dialect">
            org.hibernate.dialect.H2Dialect
        </property>

        <property name="hibernate.hbm2ddl.auto">
            create
        </property>

        <property name="hibernate.show_sql">
            true
        </property>

        <property name="hibernate.format_sql">
            true
        </property>

    </session-factory>

</hibernate-configuration>

The hibernate.cfg.xml file contains the core Hibernate configuration required to establish database connectivity and configure ORM behavior. The hibernate.connection.driver_class property specifies the H2 database driver, while hibernate.connection.url configures an in-memory H2 database named testdb. The username and password properties define the database credentials used for the connection. The hibernate.dialect property tells Hibernate which SQL dialect to generate based on the target database. The hibernate.hbm2ddl.auto property with the value create automatically creates database tables during application startup. Finally, hibernate.show_sql and hibernate.format_sql enable SQL logging and formatted SQL output in the console, making it easier to understand and debug the generated queries.

3.3 Creating the Publisher Entity

package entity;

import jakarta.persistence.*;

@Entity
public class Publisher {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    public Publisher() {
    }

    public Publisher(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

The Publisher class is a simple JPA entity representing the publisher table in the database. The @Entity annotation marks the class as a persistent Hibernate entity, while the @Id annotation identifies the primary key field. The @GeneratedValue(strategy = GenerationType.IDENTITY) configuration enables automatic ID generation using the database identity column mechanism. The name field stores the publisher name, and the class includes both a default constructor required by Hibernate and a parameterized constructor for easier object creation. The getter methods provide read access to the entity fields during persistence operations and data retrieval.

3.4 Creating the Book Entity with @NamedEntityGraph

package entity;

import jakarta.persistence.*;

@Entity

@NamedEntityGraph(
    name = "graph.book.publisher",

    attributeNodes = {
        @NamedAttributeNode("publisher")
    }
)
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @ManyToOne(fetch = FetchType.LAZY)
    private Publisher publisher;

    public Book() {
    }

    public Book(String title, Publisher publisher) {
        this.title = title;
        this.publisher = publisher;
    }

    public Long getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public Publisher getPublisher() {
        return publisher;
    }
}

The Book entity represents a book record in the database and demonstrates how to define a reusable entity graph using @NamedEntityGraph. The @Entity annotation marks the class as a Hibernate-managed entity, while the @Id and @GeneratedValue annotations configure the primary key generation strategy. The @NamedEntityGraph annotation defines a fetch graph named graph.book.publisher that instructs Hibernate to eagerly load the publisher association whenever this graph is applied. The @NamedAttributeNode("publisher") declaration specifies that the publisher relationship should be included in the fetch plan. The @ManyToOne(fetch = FetchType.LAZY) mapping creates a many-to-one relationship between Book and Publisher and uses lazy loading by default to avoid unnecessary queries. The constructors and getter methods are used for entity initialization and accessing persisted data.

3.5 Creating the Author Entity

package entity;

import jakarta.persistence.*;
import java.util.List;

@Entity

@NamedEntityGraph(
    name = "graph.author.books",

    attributeNodes = {
        @NamedAttributeNode("books")
    }
)
public class Author {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(
        cascade = CascadeType.ALL,
        fetch = FetchType.LAZY
    )
    private List<Book> books;

    public Author() {
    }

    public Author(String name, List<Book> books) {
        this.name = name;
        this.books = books;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public List<Book> getBooks() {
        return books;
    }
}

The Author entity represents an author and demonstrates how to configure an entity graph for loading related collections efficiently. The @Entity annotation marks the class as a persistent Hibernate entity, while @Id and @GeneratedValue configure automatic primary key generation. The @NamedEntityGraph annotation defines a reusable fetch graph named graph.author.books that includes the books association in the fetch plan. The @NamedAttributeNode("books") declaration tells Hibernate to eagerly load the associated books whenever this graph is applied. The @OneToMany mapping establishes a one-to-many relationship between Author and Book, where one author can have multiple books. The cascade = CascadeType.ALL configuration automatically propagates persistence operations such as save, update, and delete from the author entity to its books, while fetch = FetchType.LAZY ensures that books are loaded only when required unless overridden by the entity graph. The constructors and getter methods allow Hibernate and application code to create and access entity data.

3.6 Building the Hibernate Utility Class

package util;

import entity.Author;
import entity.Book;
import entity.Publisher;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateUtil {

    private static final SessionFactory sessionFactory;

    static {

        Configuration configuration =
                new Configuration();

        configuration.configure();

        configuration.addAnnotatedClass(Author.class);
        configuration.addAnnotatedClass(Book.class);
        configuration.addAnnotatedClass(Publisher.class);

        sessionFactory =
                configuration.buildSessionFactory();
    }

    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }
}

The HibernateUtil class is a utility class responsible for creating and managing the Hibernate SessionFactory, which is the central component used for database interactions. The Configuration object loads Hibernate settings from the hibernate.cfg.xml file using the configure() method. The addAnnotatedClass() methods register the entity classes Author, Book, and Publisher so Hibernate can detect their mappings and manage them as persistent entities. The buildSessionFactory() method creates a single shared SessionFactory instance that is initialized only once inside the static block for better performance and resource management. Finally, the getSessionFactory() method provides global access to the configured SessionFactory throughout the application.

3.7 Implementing the Main Application

import entity.Author;
import entity.Book;
import entity.Publisher;

import jakarta.persistence.EntityGraph;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.graph.GraphParser;

import util.HibernateUtil;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Main {

    public static void main(String[] args) {

        SessionFactory sessionFactory =
                HibernateUtil.getSessionFactory();

        saveData(sessionFactory);

        fetchUsingNamedEntityGraph(sessionFactory);

        fetchUsingGraphParser(sessionFactory);

        mergeGraphs(sessionFactory);

        sessionFactory.close();
    }

    private static void saveData(SessionFactory sessionFactory) {

        Session session = sessionFactory.openSession();

        Transaction tx = session.beginTransaction();

        Publisher publisher =
                new Publisher("O'Reilly");

        Book book1 =
                new Book(
                        "Hibernate Deep Dive",
                        publisher
                );

        Book book2 =
                new Book(
                        "Spring Boot Recipes",
                        publisher
                );

        Author author =
                new Author(
                        "John Doe",
                        List.of(book1, book2)
                );

        session.persist(publisher);

        session.persist(author);

        tx.commit();

        session.close();

        System.out.println("\\n===== SAMPLE DATA SAVED =====\\n");
    }

    private static void fetchUsingNamedEntityGraph(
            SessionFactory sessionFactory) {

        System.out.println(
                "\\n===== USING @NamedEntityGraph =====\\n"
        );

        Session session = sessionFactory.openSession();

        EntityGraph<?> graph =
                session.getEntityGraph(
                        "graph.author.books"
                );

        Map<String, Object> hints =
                new HashMap<>();

        hints.put(
                "jakarta.persistence.fetchgraph",
                graph
        );

        Author author =
                session.find(
                        Author.class,
                        1L,
                        hints
                );

        System.out.println(
                "Author : " + author.getName()
        );

        for (Book book : author.getBooks()) {

            System.out.println(
                    "Book : " + book.getTitle()
            );
        }

        session.close();
    }

    private static void fetchUsingGraphParser(
            SessionFactory sessionFactory) {

        System.out.println(
                "\\n===== USING GraphParser =====\\n"
        );

        Session session = sessionFactory.openSession();

        EntityGraph<Author> graph =
                GraphParser.parse(
                        Author.class,
                        "books.publisher",
                        session
                );

        Map<String, Object> hints =
                new HashMap<>();

        hints.put(
                "jakarta.persistence.fetchgraph",
                graph
        );

        Author author =
                session.find(
                        Author.class,
                        1L,
                        hints
                );

        System.out.println(
                "Author : " + author.getName()
        );

        for (Book book : author.getBooks()) {

            System.out.println(
                    "Book : " + book.getTitle()
            );

            System.out.println(
                    "Publisher : " +
                    book.getPublisher().getName()
            );
        }

        session.close();
    }

    private static void mergeGraphs(
            SessionFactory sessionFactory) {

        System.out.println(
                "\\n===== MERGING ENTITY GRAPHS =====\\n"
        );

        Session session = sessionFactory.openSession();

        EntityGraph<Author> graph1 =
                session.createEntityGraph(
                        Author.class
                );

        graph1.addAttributeNodes("books");

        EntityGraph<Book> graph2 =
                session.createEntityGraph(
                        Book.class
                );

        graph2.addAttributeNodes("publisher");

        Map<String, Object> hints =
                new HashMap<>();

        hints.put(
                "jakarta.persistence.fetchgraph",
                graph1
        );

        Author author =
                session.find(
                        Author.class,
                        1L,
                        hints
                );

        System.out.println(
                "Merged Graph Loaded Successfully"
        );

        System.out.println(
                "Author : " + author.getName()
        );

        for (Book book : author.getBooks()) {

            System.out.println(
                    "Book : " + book.getTitle()
            );
        }

        session.close();
    }
}

The Main class demonstrates the complete workflow of using @NamedEntityGraph, GraphParser, and dynamic entity graphs in Hibernate. The application first retrieves the shared SessionFactory instance from the HibernateUtil class and then executes different methods to persist and fetch data. The saveData() method creates sample Publisher, Book, and Author entities, opens a Hibernate session, starts a transaction, persists the entities into the database, and commits the transaction. The fetchUsingNamedEntityGraph() method retrieves the predefined entity graph named graph.author.books using session.getEntityGraph() and applies it through the jakarta.persistence.fetchgraph hint to eagerly fetch the associated books in a single optimized query. The fetchUsingGraphParser() method demonstrates dynamic graph creation using Hibernate’s GraphParser, where the expression "books.publisher" instructs Hibernate to fetch both books and their associated publishers together. The mergeGraphs() method shows how multiple entity graphs can be created programmatically using createEntityGraph() and configured dynamically for flexible fetch planning. Throughout the example, Hibernate sessions are properly opened and closed, ensuring efficient resource management and optimized query execution.

3.8 Running the Application and Understanding the Output

After running the application, Hibernate starts the in-memory H2 database, creates the required tables automatically using the entity mappings, and persists the sample Author, Book, and Publisher records into the database. The application then demonstrates different fetching strategies using @NamedEntityGraph, GraphParser, and dynamically created entity graphs. During execution, Hibernate generates optimized SQL queries with JOIN operations to eagerly fetch related entities based on the configured fetch graph.

// Output Using @NamedEntityGraph

select
    a1_0.id,
    a1_0.name,
    b1_0.Author_id,
    b1_1.id,
    b1_1.publisher_id,
    b1_1.title
from
    Author a1_0
left join
    Author_Book b1_0
        on a1_0.id=b1_0.Author_id
left join
    Book b1_1
        on b1_1.id=b1_0.books_id
where
    a1_0.id=?

// Output Using GraphParser
select
    a1_0.id,
    a1_0.name,
    b1_0.Author_id,
    b1_1.id,
    p1_0.id,
    p1_0.name,
    b1_1.title
from
    Author a1_0
left join
    Author_Book b1_0
        on a1_0.id=b1_0.Author_id
left join
    Book b1_1
        on b1_1.id=b1_0.books_id
left join
    Publisher p1_0
        on p1_0.id=b1_1.publisher_id
where
    a1_0.id=?

The console output displays the saved sample data, loaded author details, associated books, publisher information, and confirmation messages showing that the entity graphs were applied successfully.

===== SAMPLE DATA SAVED =====

===== USING @NamedEntityGraph =====

Author : John Doe
Book : Hibernate Deep Dive
Book : Spring Boot Recipes


===== USING GraphParser =====

Author : John Doe
Book : Hibernate Deep Dive
Publisher : O'Reilly

Book : Spring Boot Recipes
Publisher : O'Reilly


===== MERGING ENTITY GRAPHS =====

Merged Graph Loaded Successfully
Author : John Doe
Book : Hibernate Deep Dive
Book : Spring Boot Recipes

4. Conclusion

@NamedEntityGraph is one of the most important Hibernate performance optimization features. It provides a clean alternative to excessive JOIN FETCH queries and avoids unnecessary EAGER loading. Using GraphParser and dynamic graphs makes Hibernate applications highly flexible and scalable, especially in large enterprise systems where fetch requirements change frequently.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

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

0 Comments
Oldest
Newest Most Voted
Back to top button