JPA/Querydsl
Querydsl에서 스프링 데이터 JPA의 페이징 활용
chanhee01
2023. 8. 13. 20:19
스프링 데이터 JPA에서 페이징을 쉽게 하는 방법을 공부했는데, Querydls과 연동해서 둘의 페이징 기능을 함께 사용하는 방법이다.
MemberRepositoryCustom 인터페이스에 선언을 해준다.
public interface MemberRepositoryCustom {
Page<MemberTeamDto> searchPageSimple(MemberCond condition, Pageable pageable);
}
Impl에 구현을 해준다.
@Override
public Page<MemberTeamDto> searchPageSimple(MemberCond condition, Pageable pageable) {
QueryResults<MemberTeamDto> results = 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();
List<MemberTeamDto> content = results.getResults();
long total = results.getTotal();
return new PageImpl<>(content, pageable, total); // PageImpl<> : Page의 구현체
}
이전의 검색 코드와 동일하지만 변경된 것이 있다면 파라미터로 Pageable을 넘겨주었고, offset이랑 limit에 pageable의 요소들을 넣어줬다. 그리고 return으로 pageable의 content, pageable, total 순으로 반환해주었다.
@Test
public void searchPageSimple() {
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
em.persist(teamA);
em.persist(teamB);
Member member1 = new Member("member1", 10, teamA);
Member member2 = new Member("member2", 20, teamA);
Member member3 = new Member("member3", 30, teamB);
Member member4 = new Member("member4", 40, teamB);
em.persist(member1);
em.persist(member2);
em.persist(member3);
em.persist(member4);
// 여기부터
MemberCond condition = new MemberCond();
PageRequest pageRequest = PageRequest.of(0, 3);
Page<MemberTeamDto> result = memberRepository.searchPageSimple(condition, pageRequest);
// 여기까지
assertThat(result.getSize()).isEqualTo(3);
assertThat(result.getContent()).extracting("username").containsExactly("member1", "member2", "member3");
}
테스트 코드인데 주석처리 된 부분 사이에서 페이징이 처리된다. PageRequest()에 (page, size) 순서대로 넣어주고 그 pageRequest를 searchPageSimple의 두 번째 인자값으로 넣어주면 된다.
이 방식은 전체 목록을 count 해오기 때문에 불필요한 데이터를 조회하는 경우가 있다.
- count를 따로 가져오기
select 쿼리는 복잡한데 count 쿼리는 간단한 경우가 있을 수도 있는데, 이럴 때에는 count할 때에도 join하고 복잡해지기 때문에 최적화를 하지 못한다.
@Override
public Page<MemberTeamDto> searchPageComplex(MemberCond 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();// fetchResults()가 아니라 fetch()로 가져온다. count 빼고 content만 받아옴
long total = queryFactory
.select(member)
.from(member)
.leftJoin(member.team, team)
.where(
usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe()))
.fetchCount();
// 쿼리를 분리해서 count를 따로 쿼리로 select 해온다.
return new PageImpl<>(content, pageable, total); // PageImpl<> : Page의 구현체
}
처음 쿼리에서 fetchResult()로 받아오지 않고 fetch()로만 받아오면 자동으로 content만 받아오게 된다.이후에 count를 하는 쿼리를 따로 만들어서 총 개수를 select해오게 하면 된다.
컨트롤러에서의 실행 결과
@GetMapping("v2/members")
public Page<MemberTeamDto> searchMemberV2(MemberCond condition, Pageable pageable) {
return memberRepository.searchPageSimple(condition, pageable);
}
@GetMapping("v3/members")
public Page<MemberTeamDto> searchMemberV3(MemberCond condition, Pageable pageable) {
return memberRepository.searchPageComplex(condition, pageable);
}

v3/members로 접근했을 때에도 당연히 동일한 결과가 나온다.