이번 포스팅에서는 QueryDSL과 Spring Data JPA를 함께 사용해 페이징 처리하는 방법에 대해 알아보고자 한다.
다음은 이번 포스팅에서 사용할 QueryDSL 사용자 정의 레포지토리 인터페이스와 이를 구현한 구현체 이다.
이 내용에 대해 알고 싶으면 아래 링크를 참고 하면 도움이 된다.
2023.07.10 - [오픈소스] - QueryDSL Repository 생성하기 (사용자 정의 리포지토리)
public interface MemberRepositoryCustom {
Page<MemberTeamDto> searchPage(MemberSearchCondition condition, Pageable pageable);
}
@RequiredArgsConstructor
public class MemberRepositoryImpl implements MemberRepositoryCustom {
private final JPAQueryFactory queryFactory;
@Override
public Page<MemberTeamDto> searchPage(MemberSearchCondition condition, Pageable pageable) {
return null;
}
}
이제 이 구현체 레포지토리 MemberRepositoryImpl에 페이징을 위한 코드를 작성해 보도록 하겠다.
페이징 적용
@RequiredArgsConstructor
public class MemberRepositoryImpl implements MemberRepositoryCustom {
private final JPAQueryFactory queryFactory;
@Override
public Page<MemberTeamDto> searchPage(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(usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe()))
.offset(pageable.getOffset()) // 몇 번째 페이지부터 시작할 것 인지.
.limit(pageable.getPageSize()) // 페이지당 몇개의 데이터를 보여줄껀지
.fetch();
JPAQuery<Long> countQuery = queryFactory
.select(member.count())
.from(member)
.leftJoin(member.team, team)
.where(usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe()));
return PageableExecutionUtils.getPage(content, pageable, () -> countQuery.fetchOne());
}
private BooleanExpression usernameEq(String username) {
return hasText(username) ? member.username.eq(username) : null;
}
private BooleanExpression teamNameEq(String teamName) {
return hasText(teamName) ? team.name.eq(teamName) : null;
}
private BooleanExpression ageGoe(Integer ageGoe) {
return ageGoe == null ? null : member.age.goe(ageGoe);
}
private BooleanExpression ageLoe(Integer ageLoe) {
return ageLoe == null ? null : member.age.loe(ageLoe);
}
}
조회 데이터와 전체 카운트를 분리하였다.
fetchResults() deprecated
전에는 QueryDSL에서 fetchResults() 메서드를 제공했었는데 이 메서드를 사용해 반환하게 되면 조회 데이터와 쿼리 데이터를 한번에 조회하는 것이였다.
그런데 이 방식이 성능에 문제가 있었는지 현재는 deprecated 되었다.
그래서 현재는 위와 같은 방식으로 조회 데이터와 카운트 쿼리를 분리하는 방법을 사용해야 한다.
PageableExecutionUtils
PageableExecutionUtils 를 사용해 반환하면 content(조회 데이터)와 pageable의 total size를 보고
페이지의 시작이면서 컨텐츠 사이즈가 페이지 사이즈보다 작거나 마지막 페이지 일 때 카운트 쿼리를 카운트 쿼리를 실행하지 않는다.
따라서 PageableExecutionUtils을 사용해 반환할 시 성능 최적화가 발생한다.
페이징 사용
이제 컨트롤러를 만들어 Postman으로 페이징이 잘 적용되었는지 확인해보자.
(현재 테스트용 데이터를 넣어둔 상태이다.)
Controller
@GetMapping("/v1/members")
public Page<MemberTeamDto> searchMemberPaging(@ModelAttribute MemberSearchCondition condition, Pageable pageable) {
return memberRepository.searchPage(condition, pageable);
}
Postman 요청
이제 해당 URL로 GET 요청을 보내보자.
(파라미터 없이 요청하면 기본값으로 0번째 페이지부터 20개의 데이터를 보여준다.)
다음은 위 처럼 파라미터를 주어 요청한 결과이다.
첫 번째 페이지부터 3개의 페이지가 보여지는 것을 확인할 수 있다.
그 외에도 pageable이 제공하는 다양한 데이터가 있다.
{
"content": [
{
"memberId": 1,
"username": "member0",
"age": 0,
"teamId": 1,
"teamName": "teamA"
},
{
"memberId": 2,
"username": "member1",
"age": 1,
"teamId": 2,
"teamName": "teamB"
},
{
"memberId": 3,
"username": "member2",
"age": 2,
"teamId": 1,
"teamName": "teamA"
}
],
"pageable": {
"sort": {
"empty": true,
"sorted": false,
"unsorted": true
},
"offset": 0,
"pageNumber": 0,
"pageSize": 3,
"paged": true,
"unpaged": false
},
"totalElements": 100,
"totalPages": 34,
"last": false,
"size": 3,
"number": 0,
"sort": {
"empty": true,
"sorted": false,
"unsorted": true
},
"first": true,
"numberOfElements": 3,
"empty": false
}
주의사항
주의할점으로, Spring Data JPA의 Sort를 QueryDSL에서 사용할 수 없다.
이유는 다음과 같다.
Spring Data JPA의 Sort 기능은 Spring Data JPA 프레임워크에서 제공하는 메서드로 정렬을 처리하는 반면,
QueryDSL은 별도의 쿼리 생성 매커니즘을 사용하여 쿼리를 생성하고 실행할 수 있다.
그렇기 때문에, QueryDSL에서 직접적으로 Spring Data JPA의 Sort 기능을 사용할 수 없고, 이를 직접 변환하여 호환되는 QueryDSL의 OrderSpecifier 클래스로 만들어 적용해야 한다.
다음 포스팅에서 JPA의 Sort 객체를 OrderSpecifier 객체로 변환하는 기능을 갖는 메서드를 만들어 QueryDSL에서 동적 정렬 기능을 추가하는 방법에 대해 설명해보려 한다.
'◼ 오픈소스' 카테고리의 다른 글
[Redis] Redis에 로그인 Session정보를 저장하여 사용하기 (세션 불일치 해결) (0) | 2023.09.11 |
---|---|
[TinyMce] 텍스트 에디터 적용하기 (+ 에디터 이미지 업로드) (0) | 2023.08.03 |
QueryDSL 동적 쿼리를 작성하는 방법에 대해 알아보자. (0) | 2023.07.11 |
QueryDSL 기본 문법 총정리 (0) | 2023.07.10 |
QueryDSL 사용법, DTO 반환 방법 정리 (0) | 2023.07.10 |