DEV Community

Sadiul Hakim
Sadiul Hakim

Posted on

Understanding and Solving the N+1 Problem in Spring Data JPA

What Is the N+1 Problem?

The N+1 SELECT problem happens when JPA (or Hibernate) executes one query to fetch a list of entities, and then N additional queries — one for each entity — to load their related data.

It’s called “N+1” because you get:

  • 1 query to fetch the parent entities
  • N queries to fetch each child entity or lazy association

This usually happens because of lazy loading (the default in JPA for @OneToMany and @ManyToOne relationships).


Example Scenario

Let’s imagine we have a simple blog model:

  • Author (id, name)
  • Post (id, title, content, author)

An Author has many Posts.

Entity Setup

@Entity
public class Author {

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

    private String name;

    @OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
    private List<Post> posts = new ArrayList<>();

    // getters, setters
}
Enter fullscreen mode Exit fullscreen mode
@Entity
public class Post {

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

    private String title;

    private String content;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author_id")
    private Author author;

    // getters, setters
}
Enter fullscreen mode Exit fullscreen mode

Repository Layer

public interface AuthorRepository extends JpaRepository<Author, Long> {
}
Enter fullscreen mode Exit fullscreen mode

Step 1: Reproduce the N+1 Problem

In your service or controller:

@Service
@Transactional(readOnly = true)
public class AuthorService {

    private final AuthorRepository authorRepository;

    public AuthorService(AuthorRepository authorRepository) {
        this.authorRepository = authorRepository;
    }

    public void printAuthorsAndPosts() {
        List<Author> authors = authorRepository.findAll();

        for (Author author : authors) {
            System.out.println("Author: " + author.getName());
            for (Post post : author.getPosts()) {
                System.out.println("  Post: " + post.getTitle());
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Observe the SQL Queries

Enable SQL logging in application.properties:

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.orm.jdbc.bind=TRACE
Enter fullscreen mode Exit fullscreen mode

You’ll see something like this:

select a.id, a.name from author a;
select p.id, p.title, p.content, p.author_id from post p where p.author_id = 1;
select p.id, p.title, p.content, p.author_id from post p where p.author_id = 2;
select p.id, p.title, p.content, p.author_id from post p where p.author_id = 3;
...
Enter fullscreen mode Exit fullscreen mode

That’s 1 query for authors + N queries for posts — the N+1 problem.


Why It’s a Problem

  • Causes massive performance issues when data grows.
  • Increases database round trips.
  • Makes your application slower and more resource-heavy.

Step 3: Fixing the N+1 Problem

There are multiple solutions depending on the situation.


Option 1: Use JOIN FETCH in JPQL

You can customize your query to load the posts together with authors:

@Query("SELECT a FROM Author a JOIN FETCH a.posts")
List<Author> findAllWithPosts();
Enter fullscreen mode Exit fullscreen mode

Now in your service:

List<Author> authors = authorRepository.findAllWithPosts();
for (Author author : authors) {
    System.out.println(author.getName() + " has posts: " + author.getPosts().size());
}
Enter fullscreen mode Exit fullscreen mode

Generated SQL:

select a.*, p.* 
from author a 
join post p on a.id = p.author_id;
Enter fullscreen mode Exit fullscreen mode

Only one query — no N+1 issue.


Option 2: Use @EntityGraph

JPA provides a declarative way to define which associations to fetch eagerly:

@EntityGraph(attributePaths = "posts")
@Query("SELECT a FROM Author a")
List<Author> findAllWithPosts();
Enter fullscreen mode Exit fullscreen mode

Or simpler:

@EntityGraph(attributePaths = "posts")
List<Author> findAll();
Enter fullscreen mode Exit fullscreen mode

This tells Hibernate to fetch posts together in one query.


Option 3: Use Batch Fetching (Hibernate Optimization)

If you often need to load collections lazily but want to avoid N+1, enable batch fetching in your application.properties:

spring.jpa.properties.hibernate.default_batch_fetch_size=10
Enter fullscreen mode Exit fullscreen mode

This groups lazy loads into batches:

Instead of:

SELECT * FROM post WHERE author_id = 1;
SELECT * FROM post WHERE author_id = 2;
SELECT * FROM post WHERE author_id = 3;
Enter fullscreen mode Exit fullscreen mode

You’ll get:

SELECT * FROM post WHERE author_id IN (1,2,3);
Enter fullscreen mode Exit fullscreen mode

Reduced queries, still lazy loading.


Step 4: Benchmark Difference

Scenario Queries Notes
Default (Lazy Loading) 1 + N Slowest
JOIN FETCH 1 Fastest, eager
EntityGraph 1 Declarative, flexible
Batch Fetching ~1 + N/k Balanced approach

Summary

Problem Cause Fix
N+1 SELECT Lazy loading of relationships Use JOIN FETCH, @EntityGraph, or batch fetching
When to use JOIN FETCH When you always need the association
When to use EntityGraph When you sometimes need the association
When to use Batch Fetching When you prefer lazy loading but want fewer SQL queries

Complete Example Repository

You can structure your project like this:

src/
 ├── main/java/com/example/nplus1/
 │    ├── entity/
 │    │    ├── Author.java
 │    │    └── Post.java
 │    ├── repository/
 │    │    └── AuthorRepository.java
 │    ├── service/
 │    │    └── AuthorService.java
 │    └── NPlus1Application.java
 └── main/resources/
      └── application.properties
Enter fullscreen mode Exit fullscreen mode

Final Thoughts

  • The N+1 problem is subtle and can go unnoticed in small datasets.
  • Always check your SQL logs or use Hibernate Statistics or p6spy to detect it.
  • Choose a strategy (JOIN FETCH, EntityGraph, or batch fetching) that best fits your data access pattern.

Top comments (0)