본문 바로가기

개인적으로 공부한 것을 정리해 놓은 블로그입니다 틀린 것이 있으면 댓글 부탁 드립니다!


JPA/QueryDSL

QueryDSL 공부 11 - Spring data JPA 와 Querydsl 페이징 연동

반응형

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를 사용하자

 

 

반응형