Post

JPA N+1 Problem: From Root Cause to Solutions

JPA N+1 Problem: From Root Cause to Solutions

What Is the N+1 Problem?

When fetching a parent entity and then accessing its child entities, instead of one query, N+1 queries are executed.

1
2
3
4
5
List<Product> products = productRepository.findAll();  // 1 query

for (Product product : products) {
    product.getOptions().size();  // N queries (once per product)
}

If there are 100 products, 101 queries run. This is the N+1 problem.


Why Does It Happen?

JPA @OneToMany and @ManyToOne use FetchType.LAZY by default. This is intentional - don’t load data until needed. But when you access associated entities inside a loop, queries end up firing N times.


Solution 1: Fetch Join

1
2
@Query("SELECT p FROM Product p JOIN FETCH p.options")
List<Product> findAllWithOptions();

Fetches everything in one query. But creates cartesian product, potentially duplicating parent rows.


Solution 2: @EntityGraph

1
2
3
@EntityGraph(attributePaths = {"options"})
@Query("SELECT p FROM Product p")
List<Product> findAllWithOptions();

Similar to Fetch Join, but cleaner separation between query and fetch strategy.


Solution 3: Batch Size

1
2
3
@BatchSize(size = 100)
@OneToMany(mappedBy = "product")
private List<Option> options;

Executes WHERE product_id IN (?, ?, ...) instead of individual queries. Not a single query, but significantly reduces query count.


Which to Choose?

SolutionProsCons
Fetch JoinSingle queryCartesian product, pagination issues
@EntityGraphClean codeSame limitations as Fetch Join
Batch SizeNo cartesian productNot a single query

Use Fetch Join or EntityGraph when data is small. Use Batch Size when dealing with large datasets or pagination.


Lessons Learned

  • N+1 is a common JPA pitfall, must understand it
  • Pick the right solution for the situation
  • Performance testing is necessary to verify solutions work

From Kakao Tech Campus 3rd cohort Gift API clone coding.

This post is licensed under CC BY 4.0 by the author.