Spring Boot JPA @EntityGraph, @NamedEntityGraph, and JOIN FETCH – Complete Guide with Examples

When working with Spring Boot JPA, the N+1 query problem can impact performance due to lazy loading. To optimize queries and fetch related entities efficiently, JPA provides @EntityGraph, @NamedEntityGraph, and JOIN FETCH. This guide walks you through their usage with practical examples, helping you write efficient, high-performance database queries in Spring Boot.


1. Introduction

When working with JPA and Hibernate, N+1 query issues often arise due to lazy loading.

  • @NamedEntityGraph and @EntityGraph provide a way to fetch related entities efficiently.
  • JOIN FETCH is a JPQL technique that helps load associated entities in a single query.

2. Create a Spring Boot Project

You can use Spring Initializr with:

  • Spring Boot 3+
  • Dependencies: Spring Web, Spring Data JPA, Lombok, H2 Database

3. Define JPA Entities

Department Entity (OneToMany with Employee)

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

@Data
@Entity
@Table(name = "departments")
@NamedEntityGraph(name = "Department.employees", attributeNodes = @NamedAttributeNode("employees"))
public class Department {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
    private List<Employee> employees;
}

Employee Entity (ManyToOne with Department)

import jakarta.persistence.*;
import lombok.Data;

@Data
@Entity
@Table(name = "employees")
public class Employee {

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

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "department_id")
    private Department department;
}

4. Repository Layer

Using @EntityGraph (Annotation on Query Method)

import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;

public interface DepartmentRepository extends JpaRepository<Department, Long> {

    @EntityGraph(attributePaths = {"employees"})
    List<Department> findAll();  
}

✅ This ensures employees are fetched without lazy loading.


Using @NamedEntityGraph (Defined in Entity)

import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;

public interface DepartmentGraphRepository extends JpaRepository<Department, Long> {

    @EntityGraph(value = "Department.employees", type = EntityGraph.EntityGraphType.FETCH)
    Optional<Department> findById(Long id);
}

✅ This will use NamedEntityGraph defined in the Department entity.


Using JOIN FETCH in JPQL

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;

public interface EmployeeRepository extends JpaRepository<Employee, Long> {

    @Query("SELECT d FROM Department d JOIN FETCH d.employees WHERE d.id = :id")
    Department findByIdWithEmployees(@Param("id") Long id);
}

✅ This ensures Department & Employees are loaded in one query.


5. Service Layer

import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class DepartmentService {

    private final DepartmentRepository departmentRepository;

    public DepartmentService(DepartmentRepository departmentRepository) {
        this.departmentRepository = departmentRepository;
    }

    public List<Department> getAllDepartments() {
        return departmentRepository.findAll();
    }
}

6. Controller Layer

import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/departments")
public class DepartmentController {

    private final DepartmentService departmentService;

    public DepartmentController(DepartmentService departmentService) {
        this.departmentService = departmentService;
    }

    @GetMapping
    public List<Department> getDepartments() {
        return departmentService.getAllDepartments();
    }
}

7. Testing Queries in Logs

Enable SQL logging in application.properties:

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
  • Without @EntityGraph, lazy loading causes multiple queries.
  • With @EntityGraph, JOIN FETCH, or NamedEntityGraph, only one optimized query is used.

8. Summary of Best Practices


9. Conclusion

  • If using Spring Data JPA methods, prefer @EntityGraph.
  • If using JPQL, use JOIN FETCH for optimized queries.
  • If using repeated queries, define @NamedEntityGraph.

Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete