skip to content

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