Spring Data 의 Pageble 과 Page를 Querydsl과 함께 사용하는 것을 알아보려 한다.
간단한 방법과 성능최적화를 위한 방법 두가지 방법을 알아보자
public interface MemberRepositoryCustom {
Page<MemberTeamDto> searchPageSimple(SearchCond searchCond, Pageable pageable);
Page<MemberTeamDto> searchPageComplex(SearchCond cond, Pageable pageable);
}
사용자 정의 리파지토리이다, SpringData에서 Pageable은 페이지 요청에 대한 데이터를 담을때 사용하는 인터페이스이다. 응답할때는 Page를 사용한다.
위의 두 메서드를 구현하는 구현클래스이다
@RequiredArgsConstructor
public class MemberRepositoryImpl implements MemberRepositoryCustom{
private final JPQLQueryFactory queryFactory;
//PageImple 활용
@Override
public Page<MemberTeamDto> searchPageSimple(SearchCond cond, Pageable pageable) {
QueryResults<MemberTeamDto> results = queryFactory
.select(new QMemberTeamDto(
member.id.as("memberId"),
member.age,
member.username,
team.name.as("teamName")
))
.from(member)
.leftJoin(member.team, team)
.where(
usernameEq(cond.getUsername()),
teamNameEq(cond.getTeamName()),
ageLoe(cond.getAgeLoe()),
ageGoe(cond.getAgeGoe())
)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetchResults();
List<MemberTeamDto> content = results.getResults();
long total = results.getTotal();
return new PageImpl<>(content,pageable,total);
}
//컨텐츠를 가져오는 쿼리와 카운트 쿼리 분리하고 PageableExecutionUtils를 사용하여 응답
@Override
public Page<MemberTeamDto> searchPageComplex(SearchCond cond,Pageable pageable) {
//content를 가져오는 쿼리는 fetch로 하고
List<MemberTeamDto> fetch = queryFactory
.select(new QMemberTeamDto(
member.id.as("memberId"),
member.age,
member.username,
team.name.as("teamName")
))
.from(member)
.leftJoin(member.team, team)
.where(
usernameEq(cond.getUsername()),
teamNameEq(cond.getTeamName()),
ageLoe(cond.getAgeLoe()),
ageGoe(cond.getAgeGoe())
)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
//count 만 가져오는 쿼리
JPQLQuery<Member> count = queryFactory
.selectFrom(member)
.leftJoin(member.team, team)
.where(
usernameEq(cond.getUsername()),
teamNameEq(cond.getTeamName()),
ageLoe(cond.getAgeLoe()),
ageGoe(cond.getAgeGoe())
);
return PageableExecutionUtils.getPage(fetch,pageable,()-> count.fetchCount());
//return new PageImpl<>(fetch,pageable,total);
}
private BooleanExpression ageGoe(Integer ageGoe) {
return ageGoe != null ? member.age.goe(ageGoe) : null;
}
private BooleanExpression ageLoe(Integer ageLoe) {
return ageLoe != null ? member.age.goe(ageLoe):null;
}
private BooleanExpression teamNameEq(String teamName) {
return hasText(teamName) ? team.name.eq(teamName):null;
}
private BooleanExpression usernameEq(String username) {
return hasText(username) ? member.username.eq(username):null;
}
}
1. PageImpl로 응답데이터 리턴(간단한 방법)
searchPageSimple를 먼저보면 쿼리를 만들고 마지막에 fetchResults()로 데이터를 가져온다 . fetchResults는 페이징 정보를 함께 리턴해주는 메서드이다 fetchResults를 사용하면 쿼리가 자동으로 2방이 나간다(total을 구하는 쿼리와 , 컨텐츠를 가져오는 쿼리 ) 쿼리를 손볼 수 없이 쿼리 코드를 만들어 놓은대로 2방 동일하게 쿼리가 생성된다.
하지만 사실 카운트 쿼리는 Dto 조회를 할 필요도 없고 페이지 사이즈 보다 토탈카운트가 적은 경우라던가 마지막 페이지일 경우에는 카운트 쿼리가 필요 없다 해결 방안은 searchPageComplex를 살펴보며 알아보고 일단 마무리
PageImpl은 Page인터페이스의 구현체이다 PageImpl에 첫번째 인자로는 content(조회된 컨텐츠),Pageable(요청으로부터 가져온 페이지 요청데이터), totalCount(전체 컨텐츠의 개수)를 주면 된다. 페이징 데이터가 많지않거나 접속량이 중요하지 않은 곳이라면 해당 방법을 사용해도 문제 없을 것 같다 .
2.카운트 , 컨텐츠 쿼리분리 PageableExecutionUtils 사용
searchPageSimple에서 성능 최적화를 한 것이 searchPageComplex이다
searchPageComplex를 살펴보자
일단 첫번째로는 fetch를 통해 컨텐츠만 가져오는 쿼리를 날리고 , 카운트쿼리에는 fetch같은게 안붙어 있다
일단 쿼리를 분리 해서 각각 날리기 때문에 카운트 쿼리에서 튜닝이 가능하다.
리턴되는 부분이 중요하다
return PageableExecutionUtils.getPage(fetch,pageable,()-> count.fetchCount());
PageableExecutionUtils.getPage는 PageImpl과 같은 역할을 하지만 한가지 기가막힌 점은 마지막 인자로 함수를 전달하는데 내부 작동에 의해서 토탈카운트가 페이지사이즈 보다 적거나 , 마지막페이지 일 경우 해당 함수를 실행하지 않는다 쿼리를 조금더 줄일 수 있다 . 위의 코드의 경우 카운트 쿼리 마지막에 fetchCount()가 여기서 관리되고 있다. PageableExecutionUtils.getPage을 사용하면 조금 더 성능 최적화가 된다. 쿼리하나가 아쉬울 때는 PageImpl 보다는 PageableExecutionUtils.getPage를 사용하자
아래 블로그에 QueryDSL에 대해 잘 소개되어있고, 설명되어있습니다.
QueryDSL을 검색하다 들어오신 분은 아래 블로그를 참고하시면 좋을 것 같습니다
출처 : https://ugo04.tistory.com/142
'Back-End > Spring' 카테고리의 다른 글
[Spring] 응답 문자열 한글 깨짐 문제 해결 (0) | 2023.02.09 |
---|---|
[Spring Security] 비밀번호 암호화 PasswordEncoder (0) | 2023.02.09 |
[QueryDSL]나만 어려운 검색, 페이징 QueryDSL로 해결하기 (0) | 2023.02.09 |
[QueryDSL]QueryDSL JPA 알아보기 Feat.Spring Data (0) | 2023.02.09 |
[Spring] 스프링으로 OAuth2 로그인 구현하기 - 카카오 (0) | 2023.02.09 |