[Spring] Spring DATA JDBC Pagination API

Gogh·2023년 1월 2일
1

Spring

목록 보기
13/23

🎯 목표 : Spring Pagination 적용 방법에 대한 이해

📒 Spring Pagination API


📌 Spring DATA JDBC에서의 Pagination 처리

  • Spring DATA JDBC에서 Pagination을 적용하기 위해 Pageable 인터페이스를 활용한다.
  • Pagination 기능을 적용하기 위해 Controller에서는 원하는 페이지, 슬라이스 사이즈를 요청 파라미터로 받아 서비스 계층으로 넘겨 줘야된다.
  • 파라미터를 받은 서비스 계층에서는 PageRequest 클래스를 이용하여 Database 계층으로 데이터를 넘겨준다.
  • PageRequest 클래스는 Pageable 인터페이스를 구현한 추상화 객체를 상속받은 클래스다.
    • 요청 페이지, 슬라이스 사이즈, 정렬기준 등으로 다양한 파라미터로 객체를 반환 받을수 있다.
  • Database 계층에서는 PageRequest 데이터를 받아 필요로 하는 데이터를 넘겨주기 위해 적절한 쿼리문을 날려줘야 되는데,
  • CrudRepository를 상속받아 Repository를 구현 했으므로 메소드 명을 적절히 작성하여 필요로 하는 데이터를 DB로 요청 해야한다.
  • 위 과정을 거쳐 반환받는 데이터 타입은 Page<T> 타입으로 클라이언트로 응답하기 위해서는 적절한 데이터 가공이 필요하다.

image

  • 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의 내부 클래스 PageInfoPage<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);
    }
profile
컴퓨터가 할일은 컴퓨터가

0개의 댓글