jpaspring
JPA N+1, 발견부터 처리까지 실전 순서
N+1은 개념은 다들 아는데, 실무에서 마주치면 "그래서 뭐부터 하지"가 된다. 내가 쓰는 순서.
1. 먼저 눈으로 확인
배포 전에 잡는 게 최고다. 테스트 프로파일에 쿼리 카운터를 붙인다.
spring:
jpa:
properties:
hibernate.generate_statistics: true
통계 로그에서 n query executions가 리스트 크기에 비례해서 늘면 확정.
2. 해결 우선순위
① fetch join — 딱 그 화면에서만 필요하면 제일 명시적이다.
@Query("select p from Post p join fetch p.author where p.status = :status")
List<Post> findPublishedWithAuthor(PostStatus status);
단, 컬렉션 fetch join + 페이징은 금지다. 하이버네이트가 메모리에서 페이징한다(로그에 HHH000104 뜨면 그 상황).
② @BatchSize / default_batch_fetch_size — 컬렉션이 얽히면 이게 현실적이다.
spring.jpa.properties.hibernate.default_batch_fetch_size: 100
지연 로딩을 IN 쿼리로 묶어줘서 N+1이 1+1로 줄어든다. 전역으로 깔아두면 사고가 잘 안 난다.
③ DTO 프로젝션 — 조회 화면이라면 엔티티 대신 필요한 컬럼만.
@Query("""
select new com.backtick.dto.PostCard(p.title, p.slug, a.handle)
from Post p join p.author a
""")
List<PostCard> findCards();
3. 재발 방지
리뷰에서 잡는 것도 좋지만, 쿼리 수를 단언하는 테스트가 더 오래 간다. 하이버네이트 Statistics의 getPrepareStatementCount()를 단언에 쓰면 된다.
정리: 통계로 발견 → fetch join(단건) / batch size(전역) / DTO(조회) → 쿼리 수 테스트로 고정.
조회 1