이번 포스팅에서는 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 |