본문 바로가기
QueryDSL

QueryDsl 페이징 처리

by 이상한나라의개발자 2024. 4. 1.

이번 포스팅에서는 Springboot + Querydsl을 활용한 pageing 처리를 어떻게 하는지 살펴보겠습니다.

 

DTO 설계

Request DTO 설계

조회 조건과 페이징 요청을 담은 객체를 설정합니다. 이때, 페이징 속성은 공통을 사용하기 위해 상속을 받습니다.

@Data
@NoArgsConstructor
public class MemberSearchCond extends PageRequestDto {

    private String username;
    private String teamName;
    private Integer ageGoe;
    private Integer ageLoe;
}

 

 

PageRequestDto 클래스는 페이징 처리를 위한 요청 데이터 속성을 설정합니다. 이 클래스는 클라이언트로 부터 페이징 관련 요청 정보( 페이지 크기, 번호, 정렬 방향, 정렬 기준) 를 담고 있으며, 이를 기반으로 Spring Data JPA의 Pageble 인터페이스 구현체를 사용하는 데 사용됩니다. 

@Data
@NoArgsConstructor
public class PageRequestDto {

    private static final int DEFAULT_PAGE = 1;
    private static final int DEFAULT_SIZE = 10;
    private static final Sort.Direction DEFAULT_DIRECTION = Sort.Direction.ASC;
    private static final String DEFAULT_SORT_BY = "id";

    private int page = DEFAULT_PAGE;
    private int size = DEFAULT_SIZE;
    private Sort.Direction direction = DEFAULT_DIRECTION;
    private String sortBy = DEFAULT_SORT_BY; // 기본 정렬 필드

    // 페이지 사이즈 0 또는 음수일 경우 방어로직
    public void setPage(int page) {
        this.page = Math.max(page, 1);
    }

    // 페이지 사이즈 0 또는 음수일 경우 방어로직
    public void setSize(int size) {
        this.size = Math.max(size, 1);
    }
    public Pageable toPageable() {
        return PageRequest.of(page - 1, size, direction, sortBy);
    }
}

 

page : 클라이언트가 요청한 페이지 번호 입니다. 페이지 번호는 1부터 시작하지만, Spring Data JPA에서는 페이지 인덱스가 0부터 시작하므로 내부적으로 변환 작업을 합니다.

size : 한 페이지에 표시될 row 수입니다. 이 값을 통해 한 페이지당 몇 개의 데이터를 불러올지 결정할 수 있습니다.

direction : 데이터 정렬 방향을 나타냅니다. Sort.Direction.ASC 는 오름차순 Sort.Direction.DESC는 내림차순 정렬을 의미합니다.

sortBy : 정렬 기준이 되는 필드의 이름입니다. 예를 들어, id or name 같 같은 엔티티의 속성 이름이 될 수 있습니다.

 

Controller

@GetMapping(value = "/searchMemberPage")
public ResponseEntity<Page<MemberDto>> searchMemberPage(@RequestBody MemberSearchCond memberSearchCond) {
    Page<MemberDto> result = memberService.searchMemberPage(memberSearchCond, memberSearchCond.toPageable());
    return ResponseEntity.ok(result);
}

 

Service

public Page<MemberDto> searchMemberPage(MemberSearchCond memberSearchCond, Pageable pageable) {
    return memberRepository.searchMemberPage(memberSearchCond, pageable);
}

 

Repository

이 코드는 사용자로 부터 받은 검색 조건과 페이징 정보를 기반으로 검색하고 검색 결과를 페이징 처리하여 반환합니다.

public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
}

 

public interface MemberRepositoryCustom {
    Page<MemberDto> searchMemberPage(MemberSearchCond memberSearchCond, Pageable pageable);
}

 

@Override
public Page<MemberDto> searchMemberPage(MemberSearchCond memberSearchCond, Pageable pageable) {


    List<MemberDto> result = queryFactory
            .select(
                    Projections.fields(MemberDto.class,
                            member.username,
                            member.age,
                            team.name.as("teamName")
                    )
            )
            .from(member)
            .join(member.team, team)
            .where(
                    usernameEq(memberSearchCond.getUsername()),
                    teamNameEq(memberSearchCond.getTeamName()),
                    ageGoe(memberSearchCond.getAgeGoe()),
                    ageLoe(memberSearchCond.getAgeLoe()))
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .fetch();


    JPAQuery<Long> totalCount = queryFactory
            .select(member.count())
            .from(member)
            .join(member.team, team)
            .where(
                    usernameEq(memberSearchCond.getUsername()),
                    teamNameEq(memberSearchCond.getTeamName()),
                    ageGoe(memberSearchCond.getAgeGoe()),
                    ageLoe(memberSearchCond.getAgeLoe())
            );

    return PageableExecutionUtils.getPage(result, pageable, () -> totalCount.fetch().size());
    //return new PageImpl<>(result, pageable, count);
}
private BooleanExpression ageLoe(Integer ageLoe) {
    if (ageLoe != null) {
        return member.age.loe(ageLoe);
    }
    return null;
}

private BooleanExpression ageGoe(Integer ageGoe) {
    if (ageGoe != null) {
        return member.age.goe(ageGoe);
    }
    return null;
}

private BooleanExpression teamNameEq(String teamName) {
    if (StringUtils.hasText(teamName)) {
        return team.name.eq(teamName);
    }
    return null;
}

private BooleanExpression usernameEq(String username) {
    if (StringUtils.hasText(username)) {
        return member.username.eq(username);
    }
    return null;
}

 

페이징 결과 반환

PageableExecutionUtils.getPage() 을 사용하여 페이징 결과를 반환합니다. result, pageable, totalCount.fetch().size() 등을 전달하여 page 인터페이스르 구현하는 객체를 반환합니다. 이 방식은 실제 페이지 데이터와 총 데이터 수가 필요할 때만 총 개수를 조회하여 성능을 최적화합니다. 예를 들어, 전체 데이터가 10인데, size가 10이라면 totalCount 쿼리는 실행되지 않습니다.

 

위 기능을 사용하기 위해서 JPAQuery를 이용하며, 해당 클래스는 쿼리 조각을 미리 조립하고 totalCount.fetch().size() 함수가 호출될 때 실제 쿼리가 수행됩니다.

 

BooleanExpression 조건 메서드

이 표현식은 쿼리의 where 조건에 사용됩니다. 각 메서드는 해당 필드에 대한 조건이 null이 아닐 때만 적용되며, 문자열 필드의 경우 비어 있지 않은 문자열에 대해서만 조건을 적용합니다. 

'QueryDSL' 카테고리의 다른 글

QueryDsl에서 함수 사용  (0) 2024.03.27
QueryDsl 동적쿼리 작성 방법  (1) 2024.03.26
QueryDsl 프로젝션 결과 반환  (0) 2024.03.25
QueryDsl 서브쿼리  (0) 2024.03.20
QueryDsl 조인  (0) 2024.03.20