스프링/QueryDsl

[QueryDsl] QueryDsl + Paging & CountQuery 최적화

nomoreFt 2022. 3. 22. 14:42

SpringData의 Page 인터페이스와 Pageable 인터페이스,
그리고 Page 구현체 public PageImpl(List<T> content, Pageable pageable, long total)
PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne) 로 경우에 따라 Count쿼리 최적화
를 사용한다.

count 분리 안하고 한번에 조회

Pageable interface로 page,size,sort를 자동 매핑하여 사용하게 하고,
getOffset(), getPageSize() 등으로 꺼내 queryDsl의 offset,limit 메서드등에 인자로 넣어준다.
Page 객체로 return해주기 위해 PageImpl<>(실 return된 내용(Member), 인자로 받은 pageable, 전체 카운트)를 new해서 반환

✨주의사항 , QueryDsl의 fetchResults()가 곧 사라진다. <group By 등 복잡한 쿼리에서 count 쿼리를 자동 생성하는데 오류나는듯>
-> 그냥 fetch()를 쓰고 자바에서 결과 counting을 하여 사용하란 대안이다.
-> 근데 size()는 단순히 자바에서 List의 크기만 가져와서 총 total count는 안되니, 그냥 count query 따로 사용하자.

  • fetchResult() 예시 (사장된다 곧 deprecated)
@Override
public Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition,Pageable pageable){
        QueryResults<MemberTeamDto> result=queryFactory
        .select(new QMemberTeamDto(member.id.as("memberId")
            ,member.username
            ,member.age
            ,team.id.as("teamId")
            ,team.name.as("teamName")))
        .from(member)
        .leftJoin(member.team,team)
        .where(usernameEq(condition.getUsername())
                        ,teamNameEq(condition.getTeamName())
                        ,ageGoe(condition.getAgeGoe())
                        ,ageLoe(condition.getAgeLoe()))
        .offset(pageable.getOffset())
        .limit(pageable.getPageSize())
        .fetchResults();
        //fetch가 아닌 fetchResult를 써야 count, page 쿼리 두개를 날린다.
        List<MemberTeamDto> content = result.getResults();
        long total = result.getTotal(); 
        return new PageImpl<>(content, pageable, total); 
}
  • fetchCount() -> fetch()로 변경, java에서 counting

    @Override
public Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition, Pageable pageable) {
        List<MemberTeamDto> content = 
            queryFactory
                .select(new QMemberTeamDto(
                member.id.as("memberId"),
                member.username,
                member.age,
                team.id.as("teamId"),
                team.name.as("teamName")))
                .from(member)
                .leftJoin(member.team, team)
                .where(
                        usernameEq(condition.getUsername()),
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())
                ).offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch()



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

count 쿼리 분리하여 따로 사용하는 법 (이제 이방법을 주력으로 사용해야 한다.)

Page<>에 Dto 담아 return하는 것은 동일,
Pageable 사용하는 것도 동일이다. 다만 queryDsl로 countQuery 한번 더 호출,
PageableExecutionUtils.getPage로 맨 첫 페이지 content 개수가 size 미달이거나, 마지막 page인 경우 count query 실행 X하여 최적화

1.Page<>에 Dto 담아 return하는 것은 동일,
fetch() 쿼리로 content만 우선 실행,


    @Override
    public Page<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageable pageable) {
        List<MemberTeamDto> content = queryFactory
                .select(new QMemberTeamDto(
                        member.id.as("memberId"),
                        member.username,
                        member.age,
                        team.id.as("teamId"),
                        team.name.as("teamName")))
                .from(member)
                .leftJoin(member.team, team)
                .where(
                        usernameEq(condition.getUsername()),
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())
                ).offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch(); //fetch를 해서 content만 가져온다.

    }

2-1. count 쿼리 fetchOne()으로 직접 호출

long count = queryFactory
                //.select(Wildcard.count) //select count(*)
                .select(member.count())
                .from(member)
                .leftJoin(member.team, team)
                .where(
                        usernameEq(condition.getUsername()),
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())
                ).fetchOne();

2-2. fetch 안하고 JPAQuery count query만 뽑아서 PageableExecutionUtils.getPage() 호출

이 방법이 count를 경우에 따라 호출하기 때문에 최적화라 생각한다.

        JPAQuery<Long> countQuery = queryFactory
                .select(member.count())
                .from(member)
                .where(
                        usernameEq(condition.getUsername()),
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())

                );

        return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne);