Post

03 JPA N+1

03 JPA N+1

1. Note

1. Note

  • 마이바티스의 경우에는 SQL을 직접 작성해야하는 불편하지만 복잡한 SQL에서는 편한 편이고,
  • 단순한 SQL에서는 JPA가 압도적으로 편한 패턴을 가지는 듯함.
  • N+1 패턴은 일단 ORM의 구조적 trade-off일 뿐 해결 불가능한 문제는 아니기 때문에 사용함.
  • ORM이 표준이고 반드시 써야한다는 것에 대해서는 모르겠음 (2026.03.17.)
    • 상황에 따라서 필요에 따라서 시스템에 맞는 방식을 활용해야할듯 하고,
    • MSA면 서비스에 따라서 SQL이 메인으로 활용하는 서비스에서는 분리하고,
    • 로직과 단순한 흐름과 관련된 서비스에서는 JPA를 써야하지 않을까.
  • 어떻게 해야 메리트가 있는거지?

2. 02JPA_Join -> 1.Note

  • Join을 사용하다보면 N+1 패턴이 발생하고 그와 관련된 여러가지 개념이 필요
  • 상황
    • user(1)와 Order(N) 테이블이 있을때 Lazy 전략을 사용할 경우에,
    • 초기에 User를 조회하면, 캐시에 user 테이블정보만 있고 Order는 없음
    • 이때 Order가 필요한 경우가 생겨버리면,
    • 필요한 Order만큼 N번씩 조회하는 상황이 생김.
    • 나는 조회를 1번했지만 캐시에 전체 Order가 없기 때문에 필요한 order 엔티티를 계속 조회하는 상황.

2. N+1 패턴

1. N+1 패턴

  • N+1 문제는 연관 엔티티를 조회하는 과정에서 초기 조회 1번 이후 추가로 N번의 쿼리가 발생하는 패턴
  • ORM에서 발생하기 쉬운 조심해야하는 문제

2. 발생 하는 상황

1
2
3
4
5
6
  user : Order
  List<Order> orders = orderRepository.findAll();

  for (Order order : orders) { 
      order.getUser().getName();
  }
  • 영속성 컨텍스트에 엔티티가 전부 올라가 있지 않은 상태에서
  • JPA 특성상 조회에 필요한 엔티티만 DB에서 조회하기 때문에
  • 로직에서 1건씩 조회하는 패턴(For문 한정X, 특정 엔티티에 대한 접근 O)이 발생하면
  • 반복적으로 특정 엔티티를 select 해야하는 상황이 발생하는데
  • 이때, 처음 1건 + 추가조회 N건이여서 N+1 문제가 발생함.

2. JPQL

1. SQL / JPQL

  • SQL (테이블 기준) / JPQL(엔티티 기준)
  • SQL <-> JPQL
    • 기준 테이블
      1
      2
      
      orders
      user
      
    • SQL
      1
      2
      
      SELECT * FROM orders o
      JOIN user u ON o.user_id = u.id
      
    • JPQL
      1
      
      select o from Order o join o.user u
      

2. 조건 조회

1
2
  -- 엔티티기준으로 조회함.
  select o from Order o where o.status = 'READY'  

3. DTO 리턴

1
2
3
 select new com.example.OrderDto(o.id, u.name) -- OrderDto는 id와 name 필드를 가짐.
 from Order o
 join o.user u

4. Join

1
2
 select o from Order o
 join o.user u  -- 조인은 엔티티 설정값을 보고 결정함.

3. N+1 패턴의 해결

1. Fetch Join

  • 레파지토리에서 특정 엔티티를 조회함.
    1
    2
    
     @Query("select o from Order o join fetch o.user")
     List<Order> findAllWithUser();
    
  • SQL
    1
    2
    3
    
     SELECT o.*, u.*
     FROM orders o
     JOIN user u ON o.user_id = u.id
    

2. EntityGraph

  • 레파지토리에서 조회 대상설정
    1
    2
    
     @EntityGraph(attributePaths = "user")
     List<Order> findAll();
    

3. DTO 조회

  • 레파지토리에서 조회 대상을 직접 설정
    1
    2
    3
    4
    5
    6
    
    @Query("""
      select new com.example.OrderDto(o.id, u.name)
      from Order o
      join o.user u
      """)
      List<OrderDto> findOrderDtos();
    

4. Batch Size

  • 하이버네이트 설정을 변경
    1
    2
    3
    4
    
      spring:
     jpa:
       properties:
         hibernate.default_batch_fetch_size: 100
    
  • in 패턴으로 묶어서 조회하게됨.
    • 조회해야하는 건수가 많을 경우 더욱 복잡한 패턴을 가질 수 있음
    • in (‘A’, ‘B’, ‘C’)
This post is licensed under CC BY 4.0 by the author.