Mastering @EntityGraph in Spring Boot 3: Eliminate N+1 Queries Efficiently
This article explains the classic N+1 query issue in Spring Data JPA, demonstrates how JPQL JOIN FETCH and the @EntityGraph annotation can declaratively load associations, and provides advanced examples—including named entity graphs and combining @EntityGraph with custom @Query—to improve performance and code maintainability.
1. Introduction
When using JPA, entity relationships are often configured with LAZY loading to avoid unnecessary overhead. Accessing these lazy associations outside a transaction triggers LazyInitializationException, and in pagination or complex queries it leads to the N+1 query problem, severely degrading performance.
The traditional JOIN FETCH solves the issue but lacks reusability. A declarative, reusable approach is needed to control association loading precisely.
2. Practical Cases
2.1 Classic N+1 Problem
Given the following repository method:
private final OrderRepository orderRepository;
List<Order> orders = this.orderRepository.findAll();Executing this generates separate SQL statements for the t_order table, then for each order its related t_customer and t_order_item rows, resulting in 1 + N + N queries (e.g., 21 queries for 10 orders).
2.2 JPQL Optimization with JOIN FETCH
Using JPQL to fetch associations in a single query reduces round‑trips:
@Query("SELECT o FROM Order o JOIN FETCH o.customer JOIN FETCH o.items")
List<Order> findOrders();The generated SQL joins order, customer, and order_item tables, eliminating the N+1 problem.
2.3 Using @EntityGraph for Automatic Fetching
@EntityGraphlets Spring Data JPA modify queries to eagerly load specified associations without writing explicit joins.
// Load both Customer and OrderItem
@EntityGraph(attributePaths = {"customer", "items"})
List<Order> findAll();
// Load only Customer
@EntityGraph(attributePaths = {"customer"})
Optional<Order> findById(Long id);Running findAll() produces two SQL statements: one joining order with customer, and a separate query for order_item. The findById case joins only the customer table.
2.4 Advanced Usage: Named Entity Graphs
Define a reusable graph directly on the entity:
@Entity
@Table(name = "t_order")
@NamedEntityGraph(name = "Order.EG", attributeNodes = {
@NamedAttributeNode("customer"),
@NamedAttributeNode("items")
})
public class Order { }Reference it in a repository method:
@EntityGraph(value = "Order.EG", type = EntityGraph.EntityGraphType.LOAD)
List<Order> findAll();The resulting SQL loads the order together with its customer and items efficiently.
2.5 Combining @EntityGraph with Custom @Query
Custom queries can also benefit from @EntityGraph:
@Query("SELECT o FROM Order o WHERE o.status = :status")
@EntityGraph(attributePaths = {"customer"})
List<Order> findByStatus(@Param("status") Integer status);This query filters orders by status while automatically fetching the associated customer, avoiding additional joins or N+1 queries.
3. Entity Definitions and Sample Data
Entity classes used in the examples:
@Entity
@Table(name = "t_customer")
public class Customer {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String phone;
private String address;
@OneToMany(mappedBy = "customer", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Order> orders = new ArrayList<>();
}
@Entity
@Table(name = "t_order")
public class Order {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id")
private Customer customer;
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<OrderItem> items = new ArrayList<>();
@Column(columnDefinition = "int default 0")
private Integer status;
}
@Entity
@Table(name = "t_order_items")
public class OrderItem {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String productName;
private Integer quantity;
private Double unitPrice;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id")
private Order order;
}Sample table structures (images omitted for brevity) illustrate the relationships between t_customer, t_order, and t_order_items.
4. Conclusion
Using @EntityGraph —either directly or via named graphs—provides a clean, reusable way to solve the N+1 query problem in Spring Data JPA. It works seamlessly with custom JPQL queries, improves performance, and keeps repository code concise and maintainable.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
