1️⃣ 예전 방식: fetchResults()
강의나 책에서 자주 보던 코드
QueryResults<MemberTeamDto> results = queryFactory
.select(...)
.from(member)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetchResults();
List<MemberTeamDto> content = results.getResults();
long total = results.getTotal();
내부 동작
fetchResults()는 내부적으로 두 개의 쿼리를 실행한다.
- content 조회 쿼리
- count(*) 조회 쿼리
한 번에 처리해주기 때문에 코드가 짧고 편하다.
2️⃣ 문제점 (왜 Deprecated 되었는가)
❌ 1. Querydsl 5.x에서 Deprecated
공식적으로 fetchResults(), fetchCount()는 Deprecated 되었다.
이유는:
복잡한 쿼리에서 잘못된 count 쿼리가 생성될 위험
❌ 2. Count 쿼리 최적화가 불가능
예를 들어:
.leftJoin(member.team, team)
이 조인이 content 조회에는 필요하지만
count에는 필요 없을 수 있다.
하지만 fetchResults()를 사용하면
count 쿼리에도 그대로 join이 포함된다.
실제 실행 SQL:
select count(*)
from member
left join team ...
👉 count에 필요 없는 join까지 수행
👉 성능 저하 가능성
❌ 3. fetch join + 페이징 충돌
JPA 규칙상:
fetch join + pagination = 메모리 페이징
fetchResults()는 내부에서 count 쿼리까지 자동 생성하기 때문에
fetch join과 함께 사용하면 예측하기 어려운 동작이 발생할 수 있다.
실무에서 매우 위험한 패턴이다.
3️⃣ 실무 정석 패턴: content / count 분리
① Content 쿼리
List<Article> content = queryFactory
.selectFrom(article)
.leftJoin(article.author, user).fetchJoin()
.where(...)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
② Count 쿼리
Long total = queryFactory
.select(article.count())
.from(article)
.where(...)
.fetchOne();
③ Page 객체 생성
return new PageImpl<>(content, pageable, total);
4️⃣ 왜 분리하는가?
✅ 1. Count 최적화 가능
Content 쿼리:
.fetchJoin()
Count 쿼리:
.from(article) // 불필요한 join 제거
👉 count 전용 최적화 가능
👉 대용량 테이블에서 성능 차이 매우 큼
✅ 2. fetch join 안전하게 사용 가능
- content 쿼리 → fetch join 사용 가능
- count 쿼리 → fetch join 제거
명확하게 역할 분리 가능
✅ 3. 실무 표준 패턴
실무 Querydsl 페이징의 90% 이상이:
- content 쿼리
- count 쿼리
- PageImpl 생성
Spring Data JPA 공식 문서도 동일한 패턴을 권장한다.
5️⃣ 한 단계 더 발전: PageableExecutionUtils
더 발전된 방식:
return PageableExecutionUtils.getPage(
content,
pageable,
() -> queryFactory
.select(article.count())
.from(article)
.where(...)
.fetchOne()
);
장점
마지막 페이지일 경우 count 쿼리를 실행하지 않는다.
예:
- page size = 10
- 조회 결과 = 3개
👉 이미 마지막 페이지
👉 굳이 count 쿼리 실행 안 함
👉 쿼리 1번 절약
실무에서 매우 유용한 최적화 방식이다.
'프로젝트 > 스프링 부트 3 백엔드 개발자 되기 Blog + 선착순 강의 프로젝트' 카테고리의 다른 글
| 👥💡 조회수 동시성 문제 해결(원자적) (0) | 2026.02.14 |
|---|---|
| 👥💡 Spring Data JPA + Querydsl 커스텀 Repository 구조 정리 (0) | 2026.02.14 |
| 👥🚨 수정시간 및 수정하기 작성자 권한 변경 (0) | 2026.02.14 |
| 👥🚨 수정은 됐는데 화면에는 “실패”라고 뜬다? (1) | 2026.02.07 |
| 👥💡 N+1 리팩토링 과정 (1) | 2026.02.07 |