- Spring DATA JDBC에서 Pagination을 적용하기 위해 Pageable 인터페이스를 활용한다.
- Pagination 기능을 적용하기 위해 Controller에서는 원하는 페이지, 슬라이스 사이즈를 요청 파라미터로 받아 서비스 계층으로 넘겨 줘야된다.
- 파라미터를 받은 서비스 계층에서는
PageRequest
클래스를 이용하여 Database 계층으로 데이터를 넘겨준다.
PageRequest
클래스는 Pageable 인터페이스를 구현한 추상화 객체를 상속받은 클래스다.
- 요청 페이지, 슬라이스 사이즈, 정렬기준 등으로 다양한 파라미터로 객체를 반환 받을수 있다.
- Database 계층에서는 PageRequest 데이터를 받아 필요로 하는 데이터를 넘겨주기 위해 적절한 쿼리문을 날려줘야 되는데,
CrudRepository
를 상속받아 Repository를 구현 했으므로 메소드 명을 적절히 작성하여 필요로 하는 데이터를 DB로 요청 해야한다.
- 위 과정을 거쳐 반환받는 데이터 타입은
Page<T>
타입으로 클라이언트로 응답하기 위해서는 적절한 데이터 가공이 필요하다.
Page<T>
는 위와 같이 Slice
인터페이스를 상속받은 인터페이스므로 Slice
인터페이스의 getContent()
메소드를 사용하면 쉽게 List<T>
로 변환이 가능할 것이다.
- 작성한 예제에서는 필요한 데이터를 받아
PageResponseDto
객체를 생성하여 JSON 타입으로 반환해 주는 기능을 구현하였다.
📌 Repository 구현
- Member라는 Entity를 Id 기준 내림차순으로 데이터를 받기위한
CrudRepository
요청 메소드 작성.
public interface MemberRepository extends CrudRepository<Member, Long> {
Page<Member> findAllByOrderByMemberIdDesc(Pageable pageable);
}
- 위와 같은 메소드를 DB로 요청하게 되면 아래와 같은 쿼리문이 작성되어 요청 되는 것을 DEBUG 단계에서 확인 할수 있다.
SELECT "MEMBER"."NAME" AS "NAME",
"MEMBER"."PHONE" AS "PHONE",
"MEMBER"."EMAIL" AS "EMAIL",
"MEMBER"."MEMBER_ID" AS "MEMBER_ID"
FROM "MEMBER"
ORDER BY "MEMBER"."MEMBER_ID" DESC LIMIT 10
OFFSET 0;
📌 Business 계층 구현
Page<T>
를 반환하는 타입으로 비즈니스 로직을 작성하였다.
PageRequest
클래스는 Pageable 인터페이스를 구현한 추상화 객체를 상속받은 클래스다.
- API 계층에서 전달받은
page
,size
파라미터를 적용하여 객체를 생성할 수 있다.
public Page<Member> findMembers(int page, int size) {
PageRequest pageRequest = PageRequest.of(page, size);
return memberRepository.findAllByOrderByMemberIdDesc(pageRequest);
}
📌 Page 요청에 따른 ResponseDto 구현
- Page 데이터 반환을 위해 작성한 ResponseDto는 모든 Entity에 적용하기 위해 확장성을 고려하여 구현 하였다.
- Entity 응답 Dto와
Page<T>
타입의 데이터를 파라미터로하는 생성자 편의 메소드를 작성하여 응답 객체를 생성하도록 구현 하였고,
- ResponseDto의 내부 클래스
PageInfo
는 Page<T>
가 가지고 있는 데이터들의 정보를 확인하기 위해 작성 하였다.
@Getter
public class PageResponseDto {
private Object data;
private PageInfo pageInfo;
private PageResponseDto(Object data, Page page) {
this.data = data;
this.pageInfo =
new PageInfo(
page.getNumber()+1,
page.getSize(),
(int) page.getTotalElements(),
page.getTotalPages()
);
}
public static PageResponseDto of(Object data, Page page) {
return new PageResponseDto(data, page);
}
@AllArgsConstructor
@Getter
private static class PageInfo {
private int page;
private int size;
private int totalElements;
private int totalPages;
}
}
📌 Mapper 구현
- 현재 예제 프로젝트는 API 계층에서 Mapper를 DI 받아 Entity와 Dto를 맵핑 해주는 역할을 분리 하여 설계되어 있다.
Page<T>
데이터 타입을 비즈니스 계층에서 반환 받아, Controller 단에서 맵핑하여 클라이언트로 데이터를 응답 해주기 위해, default 메소드로 Mapper를 추가적으로 구현 하였다.
@Mapper(componentModel = "spring")
public interface MemberMapper {
List<MemberResponseDto> membersToMemberResponseDtos(List<Member> members);
default PageResponseDto memberPageToPageResponseDto(Page<Member> members) {
return PageResponseDto.of(
membersToMemberResponseDtos(members.getContent()),
members
);
}
}
📌 API(Controller) 계층 구현
- 요청에 따른 데이터를 넘겨주기 위한 로직은 모두 작성 되었다.
- Controller 에서 요구하는 페이지와 슬라이스 사이즈에 대한 파라미터만 받아 나머지 계층으로 데이터를 요청하기만 하면 된다.
PageResponsDto
에서 page.getNumber()+1
페이지 Number에 대한 값에 +1을 해주도록 작성 하였다.
- 이는, Pagination API에서 Page의 실제 Index 값은 0부터 시작하기 때문에 Controller에서 파라미터로 받은 요구하는 페이지의 값도 -1을 해주어 서비스 계층으로 파라미터를 넘겨준다.
- Page에 따른 데이터를 가져오기 위해서는 Pagination API에서 Page의 실제 Index 값을 서비스 계층으로 넘겨줘야 정상적으로 동작 할 것 이다.
@GetMapping
public ResponseEntity getMembers(
@Positive @RequestParam int page,
@Positive @RequestParam int size
) {
Page<Member> members = memberService.findMembers(page-1,size);
PageResponseDto response = mapper.memberPageToPageResponseDto(members);
return new ResponseEntity<>(response, HttpStatus.OK);
}