본문 바로가기
JAVA/JPA

[QueryDSL] CountQuery 최적화 - PageableExecutionUtils

by 개미가되고싶은사람 2025. 2. 7.

PageableExecutionUtils는 Spring Data에서 제공하는 효율적인 페이징 처리를 도와주는 클래스입니다. 특히 getPage() 메소드는 데이터를 가져올 때 상황에 따라 최적화하여 성능에 큰 도움이 됩니다. 일단 핵심이 되는 getPage()메소드에 들어가는 인자를 분석해 보겠습니다.

public static <T> Page<T> getPage(List<T> content, Pageable pageable, LongSupplier totalSupplier) {
	....
}

content: 한 페이지에 있는 데이터
pageable: 페이징 관련 정보를 담고 있는 Pageable 객체입니다. 이 객체는 현재 페이지 번호, 페이지 크기 등 여러 정보를 가지고 있습니다.
totalSupplier: 데이터의 총개수를 반환하는 람다 관련 인터페이스

 

 

핵심 로직 분석

public static <T> Page<T> getPage(List<T> content, Pageable pageable, LongSupplier totalSupplier) {
		...
        
// 페이징 할 필요 없는 경우 == true
if (pageable.isUnpaged()) {
	return new PageImpl<>(content, pageable, content.size());
}

// 한 페이지에 들어갈 데이터 개수 > 해당 페이지에 들어있는 데이터 개수인 경우
if (isPartialPage(content, pageable)) {

// 첫번째 페이지인 경우 == true
if (isFirstPage(pageable)) {
	return new PageImpl<>(content, pageable, content.size());
// 첫 번째 페이지가 아니면서, 조회된 데이터가 있는 경우 == true
} else if ( !content.isEmpty()) {
	return new PageImpl<>(content, pageable, pageable.getOffset() + content.size());
}
}

		// 위 조건들에 해당하지 않은 경우 
return new PageImpl<>(content, pageable, totalSupplier.getAsLong());
}

private static <T> boolean isPartialPage(List<T> content, Pageable pageable) {
	return pageable.getPageSize() > content.size();
}

private static boolean isFirstPage(Pageable pageable) {
	return pageable.getOffset() == 0;
}

}

정리하자면 PageableExecutionUtils은 상황에 따라 

  • 페이징이 필요 없는 경우 전체 데이터와 개수를 반환
  • 현재 페이지에 들어갈 데이터 개수보다 조회된 데이터 개수가 적은 경우
    • 첫 번째 페이지인지 확인하여 적절한 총 데이터 개수를 반환
    • 첫 번째 페이지가 아니고 조회된 데이터가 있는 경우 pageable.getOffset() + content.size()로 반환
      pageable.getOffset = pageNumber * pageSize 
  • 최후의 수단으로 getAsLong()을 이용해 데이터 갯수 반환

이러한 방식으로 LongSupplier를 통해 제공된 총 데이터 개수를 활용하여 불필요한 데이터 조회를 줄이고, 효율적인 페이징 처리를 가능하게 합니다.

 

 

적용 예시

    @Override
    public Page<UserPostDto> searchPageSimple(UserSearchCond userSearchCond, Pageable pageable) {
        System.out.println("-------PageableExecutionUtils 적용 전 countQuery-----");
		Long countQueryBefore = queryFactory
                .select(users.count())
                .from(users)
                .leftJoin(users.posts, posts)
                .where(
                        usernameEq(userSearchCond.getUsername()),
                        postTitleEq(userSearchCond.getPostTitle()),
                        ageGoe(userSearchCond.getAgeGoe()),
                        ageLoe(userSearchCond.getAgeLoe()))
                .fetchOne();

        System.out.println("-------PageableExecutionUtils 적용 후 countQuery-----");
        JPAQuery<Users> countQueryAfter = queryFactory
                .select(users)
                .from(users)
                .leftJoin(users.posts, posts)
                .where(usernameEq(userSearchCond.getUsername()),
                        postTitleEq(userSearchCond.getPostTitle()),
                        ageGoe(userSearchCond.getAgeGoe()),
                        ageLoe(userSearchCond.getAgeLoe()));
        return PageableExecutionUtils.getPage(content, pageable, countQueryAfter::fetchCount);

        return new PageImpl<>(content, pageable, countQueryBefore);
	}

 

 

 

 

참고자료

https://junior-datalist.tistory.com/342

 

[Querydsl] Pagination 성능 개선 part1.PageableExecutionUtils

목차 기존 : QueryDSL의 페이징 개선 : PageableExecutionUtils : new PageImpl()의 count 쿼리 개선 Test Case Test case 1. 페이지 사이즈 20 / 총 content 8개 / 첫 번째 페이지 호출 Test case 2. 페이지 사이즈 5 / 총 content 8

junior-datalist.tistory.com