[Spring] 페이징 성능 개선하기 3-1. 검색 버튼 사용시 페이지 건수 고정하기
페이징 기능을 구현하는데 있어, 페이징 쿼리 자체를 개선하는 방법도 있지만, 그 외 다른 기능을 개선하는 방법도 함께 할 수 있다.
여기서 말하는 기능은 count 쿼리이다.
일반적인 페이징 기능에 있어 데이터 조회와 함께 매번 수행되는 것이 count 쿼리이다. 해당 조건으로 조회되는 총 건수를 알아야만 아래와 같이 pageNo들을 노출시킬 수 있다.
총 몇건인지 확인하기 위해 전체를 확인해야 하기 때문에 데이터 조회만큼 오래 걸린다.
이 문제를 개선할 수 있는 방법은 크게 2가지가 있다.
1. 검색 버튼 사용시 페이지 건수 고정하기
2. 첫 페이지 조회 결과 cache 하기
3-1. 검색 버튼 사용시 페이지 건수 고정하기
굳이 사용율이 떨어지는 페이지 버튼을 위해 매번 전체 count 쿼리가 수행될 필요가 있을까를 한번 고민해야한다.
즉, 다음과 같은 상황에서 이 방법을 고려하는 것이 좋다.
- 대부분의 조회 요청이 검색 버튼(즉, 첫 조회)에서 발생하고
- 페이지 버튼을 통한 조회 요청이 소수일 경우
이럴 경우 검색 버튼을 클릭한 경우에만 Page 수를 고정하는 것이다.
즉, 다음 페이지로 이동하기 위해 페이지 버튼을 클릭했을 때만 실제 페이지 count 쿼리를 발생시켜 정확한 페이지 수를 사용하고, 대부분 요청이 발생하는 검색 버튼 클릭시에는 count 쿼리를 발생시키지 않는 것이다.
3-1-1. 구현 코드
아래는 기존 페이징 쿼리다.
public Page<BookPaginationDto> paginationCount(Pageable pageable, String name) {
JPQLQuery<BookPaginationDto> query = querydsl().applyPagination(pageable,
queryFactory
.select(Projections.fields(BookPaginationDto.class,
book.id.as("bookId"),
book.name,
book.bookNo,
book.bookType))
.from(book)
.where(book.name.like(name + "%"))
.orderBy(book.id.desc());
List<BookPaginationDto> items = query.fetch();
long totalCount = query.fetchCount(); // 전체 count
return new PageImpl<>(items, pageable, totalCount);
}
private Querydsl querydsl() {
return Objects.requireNonNull(getQuerydsl());
}
이 코드를 검색 버튼 클릭시에는 10개 페이지를 고정으로 노출하도록 개성하기 위해서는 다음의 코드가 추가되어야 한다.
1. 검색 버튼을 클릭한 경우(useSearchBtn) 10개 페이지가 노출되도록 TotalCount (fixedPageCount)를 반환한다.
2. 페이지 버튼을 클릭한 경우 실제 쿼리를 수행해 결과를 반환한다.
3. 페이지 버튼을 클릭하였지만, 전체 건수를 초과한 페이지 번호로 요청이 온 경우에는 마지막 페이지 결과를 반환한다.
이를 적용한 코드는 다음과 같다.
public Page<BookPaginationDto> paginationCountSearchBtn(boolean useSearchBtn, Pageable pageable, String name) {
JPAQuery<BookPaginationDto> query = queryFactory
.select(Projections.fields(BookPaginationDto.class,
book.id.as("bookId"),
book.name,
book.bookNo,
book.bookType))
.from(book)
.where(book.name.like(name + "%"))
.orderBy(book.id.desc())
JPQLQuery<BookPaginationDto> pagination = querydsl().applyPagination(pageable, query);
if (useSearchBtn) { // 검색 버튼 사용시
int fixedPageCount = 10 * pageable.getPageSize();
return new PageImpl<>(pagination.fetch(), pageable, fixedPageCount);
}
long totalCount = pagination.fetchCount();
Pageable pageRequest = exchangePageRequest(pageable, totalCount); // 데이터 건수 초과시 보정
return new PageImpl<>(pagination.fetch(), pageRequest, totalCount);
}
private Pageable exchangePageRequest(Pageable pageable, long totalCount) {
int pageNo = pageable.getPageNumber();
int pageSize = pageable.getPageSize();
long requestCount = (pageNo - 1) * pageSize;
if (totalCount > requestCount) {
return pageable;
}
int requestPageNo = (int) Math.ceil((double)totalCount / pageNo);
return PageRequest.of(requestPageNo, pageSize);
}
객체 지향적으로 분리하기 위해 별도의 Dto 클래스로 추출할 수 있다.
// DTO
public class FixedPageRequest extends PageRequest {
protected FixedPageRequest(Pageable pageable, long totalCount) {
super(getPageNo(pageable, totalCount), pageable.getPageSize(), pageable.getSort());
}
private static int getPageNo(Pageable pageable, long totalCount) {
int pageNo = pageable.getPageNumber();
int pageSize = pageable.getPageSize();
long requestCount = pageNo * pageSize; // pageNo:10, pageSize:10 일 경우 requestCount=90
if (totalCount > requestCount) { // 실제 건수가 요청한 페이지 번호보다 높을 경우
return pageNo;
}
return (int) Math.ceil((double)totalCount/pageNo); // 실제 건수가 부족한 경우 요청 페이지 번호를 가장 높은 번호로 교체
}
}
// repository
public Page<BookPaginationDto> paginationCountSearchBtn2(boolean useSearchBtn, Pageable pageable, String name) {
JPAQuery<BookPaginationDto> query = queryFactory
.select(Projections.fields(BookPaginationDto.class,
book.id.as("bookId"),
book.name,
book.bookNo,
book.bookType
))
.from(book)
.where(
book.name.like(name + "%")
)
.orderBy(book.id.desc());
JPQLQuery<BookPaginationDto> pagination = querydsl().applyPagination(pageable, query);
if(useSearchBtn) {
int fixedPageCount = 10 * pageable.getPageSize(); // 10개 페이지 고정
return new PageImpl<>(pagination.fetch(), pageable, fixedPageCount);
}
long totalCount = pagination.fetchCount();
Pageable pageRequest = new FixedPageRequest(pageable, totalCount);
return new PageImpl<>(querydsl().applyPagination(pageRequest, query).fetch(), pageRequest, totalCount);
}
출처
https://jojoldu.tistory.com/530
'web > Spring' 카테고리의 다른 글
[Spring] MapStruct - Entity와 DTO 매핑하기 (0) | 2022.11.07 |
---|---|
[Spring] 페이징 성능 개선하기 3-2. 첫 페이지 조회 결과 캐시하기 (0) | 2022.07.21 |
[Spring] 페이징 성능 개선하기 2. 커버링 인덱스 사용하기 (0) | 2022.07.19 |
[Spring] 페이징 성능 개선하기 1. No Offset 사용하기 (0) | 2022.07.18 |
[Spring] Assert (0) | 2022.07.15 |
댓글 개