skip to content

@EntityGraph Fetch Tuning

@EntityGraph tells a repository method which associations to fetch eagerly, which is the declarative way to kill an N+1 without changing the mapping. Any ORM performance question eventually asks for this or a join fetch.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

What is @EntityGraph in Spring Data JPA, and what problem does it solve?

level: juniorimportance: must knowfreq 70%

answer

  1. N+1 = 1 parents query + N children queries
  2. attributePaths -> LEFT JOIN in one query
  3. per-query eager, mapping stays LAZY
  4. query hint jakarta.persistence.fetchgraph
  5. opt-in instead of global EAGER

basics

~20 s

@EntityGraph is an annotation you put on a repository method to tell JPA which associations to load eagerly in one query. It fixes the N+1 select problem where each parent triggers an extra query for its children.

solid answer

~40 s

@EntityGraph is a Spring Data JPA annotation placed on a repository query method. Its attributePaths list the associations that should be fetched eagerly for that specific query, translated by Hibernate into a JOIN so the parent and its associations come back in one round trip. Its main purpose is defeating the N+1 select problem: with LAZY associations, loading N parents and then touching each parent's collection fires 1 query for the parents plus N more for the children. By declaring @EntityGraph(attributePaths = "orders"), you get the parents and their orders in a single query. It is per-query and non-invasive: you keep associations LAZY by default in the mapping and opt into eager fetching only on the methods that need it, instead of forcing FetchType.EAGER globally.

code

java · 7 lines
java
public interface AuthorRepository extends JpaRepository<Author, Long> {

    // Without this, iterating authors then a.getBooks() = N+1 queries.
    // With it, authors + their books load in a single SQL join.
    @EntityGraph(attributePaths = "books")
    List<Author> findAll();
}

go deeper

for a junior

Know the definition: annotation on a repo method, attributePaths, and that it fixes N+1 by joining in one query.

for a middle

Explain how it maps to a query hint and a LEFT JOIN, and why it beats global EAGER.

for a senior

Contrast per-query graphs against mapping-level EAGER and the maintainability win of opt-in fetch plans.

for a principal

Frame it as one tool among JOIN FETCH, @BatchSize, and projections for controlling fetch strategy across a codebase.

### The N+1 problem In JPA, associations like `@OneToMany` are `LAZY` by default (and `@ManyToOne` is `EAGER` by default, though best practice is to make everything `LAZY`). Lazy means the associated data is not loaded until you actually access it. Consider loading 10 `Author` entities and then, in a loop, reading each author's `books` collection. Hibernate runs **1** query to fetch the authors, then **1 more query per author** (10) to fetch each books collection = **N+1 = 11 queries**. This is the classic N+1 select problem: it silently balloons round trips and destroys performance under load. ### What @EntityGraph does `@EntityGraph` is a Spring Data JPA annotation you place on a repository method. Its `attributePaths` element names the associations that should be fetched **eagerly for that one query**. Under the hood Spring builds a JPA `EntityGraph` object and passes it as a query hint (`jakarta.persistence.fetchgraph`) to the `EntityManager`; Hibernate then plans the SQL with a `LEFT JOIN` (or `LEFT OUTER JOIN`) to the named associations so everything arrives in **one** SQL statement. The N+1 collapses to 1. ```java public interface AuthorRepository extends JpaRepository<Author, Long> { @EntityGraph(attributePaths = "books") List<Author> findAll(); } ``` ### Why not just make it EAGER in the mapping? Setting `@OneToMany(fetch = FetchType.EAGER)` forces the join on **every** query for that entity, even queries that never touch the collection — wasteful, and it can cause cartesian-product explosions and paging problems. `@EntityGraph` keeps the default `LAZY` and lets each **read path** declare exactly what it needs. It is a query-scoped fetch plan, not a global mapping change. ### Key terms - **Association**: a mapped relationship between entities (`@OneToMany`, `@ManyToOne`, `@ManyToMany`, `@OneToOne`). - **LAZY vs EAGER**: whether the association is loaded on demand (LAZY) or immediately (EAGER). - **N+1**: one query for the roots plus one extra per root for a lazy association. - **attributePaths**: the property names (dot-nested for depth) to fetch eagerly. ### When to use Use `@EntityGraph` on the specific read methods where you know you will use the association, so you avoid both N+1 and over-fetching on the paths that don't need it.

  • Does @EntityGraph change the entity mapping's fetch type permanently?
    No. It only affects the query it annotates. The association stays LAZY (or whatever its mapping says) for every other query.
  • How would you detect an N+1 problem in the first place?
    Enable SQL logging (Hibernate `show_sql` / statistics) or a tool like Hypersistence datasource-proxy / p6spy and watch for a burst of near-identical SELECTs per row. Integration tests can assert query counts.

context

open as a page

What goes wrong when an @EntityGraph eagerly fetches collections combined with pagination or multiple collections, and how do you handle it?

level: seniorimportance: must knowfreq 50%

basics

~20 s

Fetching two List collections at once throws MultipleBagFetchException. Paginating (Pageable) while join-fetching a collection makes Hibernate load all rows and page in memory (HHH000104 warning). Fix: fetch one collection, use Set for bags, or split into a two-step id-then-fetch query.

open as a page

How do you declare named vs ad-hoc entity graphs, and how do you fetch nested (multi-level) associations?

level: middleimportance: should knowfreq 55%

basics

~10 s

Ad-hoc: @EntityGraph(attributePaths = {"orders", "orders.items"}) directly on the method. Named: define @NamedEntityGraph on the entity, then reference it with @EntityGraph(value = "..."). Dot notation like "orders.items" fetches nested levels.

open as a page

Explain the difference between EntityGraphType.FETCH and EntityGraphType.LOAD.

level: seniorimportance: should knowfreq 45%

basics

~10 s

FETCH (the default) makes listed attributes EAGER and treats everything not listed as LAZY. LOAD makes listed attributes EAGER but leaves unlisted attributes with their mapped fetch type. FETCH gives a stricter, fully-specified plan.

open as a page

When would you choose @EntityGraph over a JPQL JOIN FETCH, @BatchSize, or a DTO projection?

level: principalimportance: should knowfreq 40%

basics

~20 s

Use @EntityGraph for a declarative eager plan on derived/named repository methods without writing JPQL. Use JOIN FETCH when you already write custom JPQL. Use @BatchSize when you page collections or fetch several to-many sides. Use DTO projections when you only need a few fields read-only.

open as a page