[ 김영한 Querydsl #10 ] - 페이징

정동욱·2023년 7월 21일
post-thumbnail

이번 글에서는 Querydsl의 페이징 처리 방법에 대해 알아보겠습니다.

Spring-data-JPA를 사용하기 때문에 Pageable 객체를 이용합니다. 지난 글에서 마찬가지로 조건을 가지고 회원을 조회하는데, offsetlimit을 이용하여 제한된 수의 회원만 조회해보겠습니다. 코드로 먼저 보겠습니다.

public class MemberRepositoryImpl implements MemberRepositoryCustom {

	@Override
	public Page<MemberTeamDto> searchMemberPagingSimple(MemberSearchCondition condition, 
														Pageable pageable) {
                                                    
    	QueryResults<MemberTeamDto> results = queryFactory
    	        .select(new QMemberTeamDto(
    	                member.id,
    	                member.username,
	                    member.age,
                    	team.id,
                	    team.name
            	))
            	.from(member)
            	.leftJoin(member.team, team)
            	.where(memberTeamDtoEq2(condition))
            	.offset(pageable.getOffset())
        	    .limit(pageable.getPageSize())
    	        .fetchResults();
	
		List<MemberTeamDto> content = results.getResults();
    	long total = results.getTotal();

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

보면 offset()과 limit()을 사용해 어디서부터 몇 개를 가져올 건지 정하고, fetchResults()로 조회를 합니다. 이렇게 되면 전체 count 수와 가져온 것들의 데이터를 한 번에 받아볼 수 있습니다. 그리고 PageImpl 객체를 리턴합니다. PageImplPage 인터페이스를 구현한 클래스로, 데이터를 페이지 단위로 반환하기 위해 사용됩니다.

하지만 현재 사용하는 Querydsl 5.x 버전에서는 위에서 사용한 fetchResults()와 함께 fetchCount() 매서드가 Deprecated된 상태이기 때문에 다른 방법을 사용하는 게 좋습니다. 바로 데이터를 받는 쿼리와 전체 수를 세는 쿼리를 각각 따로 사용하는 방법입니다. 코드로 보겠습니다.

public class MemberRepositoryImpl implements MemberRepositoryCustom {

	@Override
    public Page<MemberTeamDto> searchMemberPagingComplex(MemberSearchCondition condition,
                                                         Pageable pageable) {
        List<MemberTeamDto> content = queryFactory
                .select(new QMemberTeamDto(
                        member.id,
                        member.username,
                        member.age,
                        team.id,
                        team.name
                ))
                .from(member)
                .leftJoin(member.team, team)
                .where(memberTeamDtoEq2(condition))
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch();

        long total = queryFactory
                .select(member.count())
                .from(member)
                .leftJoin(member.team, team)
                .where(memberTeamDtoEq2(condition))
                .fetchOne();

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

참고로 위 쿼리들을 이미 MemberRepositoryCustom 인터페이스에 등록이 된 상태입니다.

public interface MemberRepositoryCustom {
    List<MemberTeamDto> searchMember(MemberSearchCondition condition);
    Page<MemberTeamDto> searchMemberPagingSimple(MemberSearchCondition condition, Pageable pageable);
    Page<MemberTeamDto> searchMemberPagingComplex(MemberSearchCondition condition, Pageable pageable);
}

그리고 이 코드들을 테스트해보면 아래와 같습니다.

@Test
void searchPagingComplexTest() {
    MemberSearchCondition condition = new MemberSearchCondition();
    PageRequest pageRequest = PageRequest.of(0, 3);

	Page<MemberTeamDto> result = 
    		memberRepository.searchMemberPagingComplex(condition, pageRequest);

    assertThat(result.getSize()).isEqualTo(3);
    assertThat(result.getContent())
            .extracting("username")
            .containsExactly("memberA", "memberB", "memberC");
}

위에서 말한 데이터와 수를 별도로 구하는 두 번째 방법이 제일 권장되는 방법이라고 합니다. 그리고 이 예제에서는 offset을 지정해 어디서부터 가져올 건지를 정하고 있는데요, 실무에서는 Cursor라는 개념을 이용해 페이징처리를 한다고 합니다. 해당 개념은 아래의 링크에서 더 자세히 알 수 있습니다.

https://daeuungcode.tistory.com/128


그리고 이 페이징 쿼리들을 API를 통해 테스트해보겠습니다.
@RestController
@RequiredArgsConstructor
public class MemberController {

    private final MemberRepository memberRepository;

    @GetMapping("/members/v2")
    public Page<MemberTeamDto> searchMemberV2(MemberSearchCondition condition,
                                              Pageable pageable) {
                                              
        return memberRepository.searchMemberPagingSimple(condition, pageable);
    }

    @GetMapping("/members/v3")
    public Page<MemberTeamDto> searchMemberV3(MemberSearchCondition condition,
                                              Pageable pageable) {
                                              
        return memberRepository.searchMemberPagingComplex(condition, pageable);
    }
}

포스트맨으로 확인해보면

정상적으로 요청이 온 것을 확인할 수 있습니다.


이번 글로 김영한님의 Querydsl 강의는 끝이 났습니다. 현재 가계부 프로젝트에서 Querydsl을 사용하는 방식으로 쿼리를 수정했습니다. 다음 강의는 김영한님의 스프링 핵심 원리 고급편이 될 것 같습니다. 다음 글에서 뵙겠습니다.

profile
거인의 어깨 위에서 탭댄스를

0개의 댓글