Page객체로 받기 위해 우리는 메소드의 return값을 PageImpl<>()라는 형태로 리턴해준다.
PageImpl은 Page객체를 Custom해주는 클래스로 Page Interface를 구현한 것이다. PageImpl<>()에서 ()안에는 총 세가지 들어가는데.
1.content : repository의 query문을 실행한 결과값
2.pageable : size, page, offset 등 Page의 설정값
3.total : 데이터의 갯수를 파악하기위한 Count 쿼리(전체 페이지 갯수를 알아야하므로)
위에서 3번째 total을 보면 Page객체를 받기위해서는 Count 쿼리가 한번 더 실행되는 것을 우리는 알 수 있다. 아래는 Page처리를 갖는 JPA를 실행시켰을 때 나오는 쿼리문이다.
그러면 Count query는 알아서 만들어지는가?? 아니다. PageImpl을 사용하고 싶으면 작성자가 따로 Count Query문을 만들서 던져줘야했다.
그래서 Query Dsl에서는 fetchcount()라는 메소드를 만들어서 던져주었다.아래의 예시 코드를 보며 이해해보자.
QUser user = QUser.user public Page<User> Paging(String condition,Pageable pageable){ Page<User> content = queryfactory.selectfrom(user).where(user.username.eq(condition).offset(pageable.getoffset()).limit(pageable.size()).fetch() Long Count = queryfactory.selectfrom(user).where(user.username.eq(condition).offset(pageable.getoffset()).limit(pageable.size()).fetchcount() return PageImpl<User>(content,pageable,Count) }
하지만 Query Dsl이 작년부터 fetchResults()와 fetchcount()를 사장시키면서 Count 쿼리를 Native 쿼리로 짜야하나는 혼동이 생기기 시작했다.
하지만 늘 그래왔듯이 우리는 해답을 찾았습니다.
두 가지 방법이 있습니다.
- fetch.size()를 사용하자.
- count 쿼리가 모든 dialect에서 또는 다중 그룹 쿼리에서 완벽하게 지원되지 않기 때문에 deprecated 되었다. fetchCount() 대신 fetch().size() 로 동일한 결과를 얻을 수 있다고 설명한다.
- JPA쿼리와 PageableExecutionUtils를 이용하자.
- PageableExecutionUtils는 springframework에서 제공하는 페이징처리를 돕는 util이다. PageableExecutionUtils.getPage(컨텐츠 내용, 해당 쿼리에서 이용한 pageable객체, 전체 데이터 개수가 필요한 경우 사용할 count JPAQuery)를 이용하면 pageable과 content를 확인하여 상황에 따라 count 쿼리를 호출하여 결과 Page 객체를 제공한다.
- 이렇게 페이징이 가능한 이유는 다음과 같다. 현재 조회된 페이지의 contents 개수가 pageable.getPageSize()보다 작은 경우
- 현재 페이지 이전 페이지 수 * 페이지 최대 사이즈 + 현재 페이지의 contents 개수 의 연산 결과가 곧 전체 contents의 개수이기 때문에 굳이 count쿼리를 조회하지 않아도 전체 count를 알 수 있다.
- 이러한 방법을 이용한 페이징은 count 쿼리에 불필요한 join을 제거할 수 있어 성능을 최적화 할 수 있다. 또한 코드 리펙토링시에 내용쿼리와 카운트쿼리를 구분지어 가독성이 좋아진다.
필자는 2번의 방법으로 성능을 최적화 할 수 있는 법을 택하였고, 아래와 같이 코드를 짜보았습니다.
public Page<Problem> getProblemSortedBySolved(String condition, Pageable pageable){ List<Problem> content1 = queryFactory.selectFrom(problem).join(solution.problemId, problem).where(containLevel(condition),containType(condition)).orderBy(getProblemSortedByLikes(condition),getProblemSortedByViews(condition),getProblemSortedByLocalTime(condition)).offset(pageable.getOffset()).limit(pageable.getPageSize()).fetch(); JPAQuery<Long> Count1 = queryFactory.select(problem.count()).from(problem).join(solution.problemId, problem).where(containLevel(condition),containType(condition)).orderBy(getProblemSortedByLikes(condition),getProblemSortedByViews(condition),getProblemSortedByLocalTime(condition)); return PageableExecutionUtils.getPage(content1,pageable,Count1::fetchOne); }
Page처리 안에서도 최적화를 위해 고민을 한번 해보는 시간이 되었다!!