Spring Pagination으로 무한스크롤 구현

컴클로딩·2022년 7월 25일
3

Spring-TIL

목록 보기
4/5
post-thumbnail

Pagination 하는법

1. 파라미터를 Pageable 객체로 받거나 sort, page, size를 따로 받기.

1-1. 파라미터를 Pageable 객체로 받기

    @GetMapping("/api/posts")
    public Page<Post> getPosts(@RequestParam String category ,
                               Pageable pageable) {
        ...생략
    }
  • Pageable 객체로 받을 때 주의할 점은 @RequestParam 없이 받아야한다.
    • @RequestParam을 넣었더니 status code 400 Bad Request 에러발생!
  • pageable을 출력하면 어떻게 출력될까?
    • ?category=ALL&page=0&size=5&sort=default
      • 참고로 controller단에서 sort를 따로 받는 코드였는데 아래와 같이 sort : default로 담긴다.
      • @GetMapping("/api/posts")
        public Page<Post> getPosts(@RequestParam String sort,
                                   @RequestParam String category ,
                                   Pageable pageable) {
            ...생략
        }
      • Page request [number: 0, size 5, sort: default: ASC]
    • ?category=ALL&page=0&size=5
      • sort 파라미터가 없다면 UNSORTED로 담긴다.
      • Page request [number: 0, size 5, sort: UNSORTED]
    • ?category=ALL&page=0&size=5&sort=createdAt
      • 만약 정렬기준이 없이 sort만 파라미터에 있다면 Default값은 ASC 오름차순이다.
      • Page request [number: 0, size 5, sort: createdAt: ASC]
    • ?category=ALL&page=0&size=5&sort=CREATED_AT,DESC
      • 만약 정렬기준을 DESC로 설정하고 싶다면 ,DESC로 추가해준다.
      • Page request [number: 0, size 5, sort: CREATED_AT: DESC]

1-2. 파라미터를 sort, page, size를 따로 받기.

  • postController.java
        @GetMapping("/api/posts")
        public Page<GetPostsResponseDto> getPosts(@RequestParam String sort,
                                                  @RequestParam String category ,
                                                  @RequestParam int page,
                                                  @RequestParam int size) {
            return postService.getPosts(sort, category, page, size);
        }
  • postrepository를 커스텀할 때 sort와 category가 필요하다. 그래서 sort부분을 따로 받아주기위해 sort, page, size를 따로 받았다.

2. Service단에서 PageRequest.of메소드로 Pageable객체 생성

  • postService.java
    public Page<GetPostsResponseDto> getPosts(@RequestParam String sort,
                                              @RequestParam String category,
                                              @RequestParam int page,
                                              @RequestParam int size) {
            Pageable pageable = PageRequest.of(page,size);
            return postRepositoryImpl.findAllByCategoryOrderBySort(sort, category, pageable);
        }
  • Pageabele 객체를 생성한 pageable을 출력해봤다.
    • Page request [number: 0, size 5, sort: UNSORTED]
    • page와 size만 들어가서 sort는 UNSORTED가 담긴다.
  • 참고로 반환타입은 Page다. 반환타입은 Slice, Page, List 3가지가 있다.

3. postRepository 커스텀 클래스에서 return해줄 때 new PageImpl<>()해서 반환!

  • postRepositoryImpl.java
        @Override
        public Page<GetPostsResponseDto> findAllByCategoryOrderBySort(String sort, String category, Pageable pageable) {
            List<GetPostsResponseDto> returnPost = queryFactory.select(Projections.fields(
                    GetPostsResponseDto.class,
                            post.title,
                            post.category,
                            post.deadline,
                            post.numberPeople,
                            post.currentNumberPeople,
                            post.contactMethod,
                            post.viewCount,
                            post.user.nickname,
                            post.imageUrl
                            ))
                    .from(post)
                    .where(categoryContains(category))
                    .orderBy(orderByValidDeadline(),getOrderSpecifier(sort))
                    .fetch();
            return new PageImpl<>(returnPost,pageable,returnPost.size());
        }
  • 여기서 가장 중요한 부분은 return부분이다.
    • return new PageImpl<>(returnPost,pageable,returnPost.size());
    • PageImpl<>(select 결과값, 생성된 pageable 객체, select 결과값의 크기);

결과값

{
    "content": [
        {
            "title": "amo",
            "category": "DELIVERY",
            "deadline": 1663243762507,
            "numberPeople": 9,
            "currentNumberPeople": 4,
            "viewCount": 65,
            "nickname": "123",
        },
        {
            "title": "dpu",
            "category": "EXHIBITION",
            "deadline": 1660593585499,
            "numberPeople": 4,
            "currentNumberPeople": 3,
            "viewCount": 40,
            "nickname": "123",
        },
      ...
    ],
    "pageable": {
        "sort": {
            "empty": true,
            "sorted": false,
            "unsorted": true
        },
        "offset": 0,
        "pageNumber": 0,
        "pageSize": 5,
        "paged": true,
        "unpaged": false
    },
    "last": false,
    "totalElements": 99,
    "totalPages": 20,
    "size": 5,
    "number": 0,
    "sort": {
        "empty": true,
        "sorted": false,
        "unsorted": true
    },
    "first": true,
    "numberOfElements": 99,
    "empty": false
}
  • 결과값을 보면 자동으로 select결과값은 contents로 싸여지고 자동으로 "pagealbe", "last", "totalElements"..등이 생성된다.
    • last(boolean타입) : 이 페이지가 마지막인가?
    • totalElements(int타입) : 요소의 총 수, 즉 contents의 크기 혹은 길이
    • totalPages : 만들 수 있는 페이지 총 수
    • size(int타입) : 페이지 당 나타낼 수 있는 요소 수(참고로 default : 20)
    • number(int타입) : 현재 페이지번호
    • first(boolean타입) : 첫 번째 페이지인가?
    • numberOfElements(int타입) : 실제 데이터 개수
    • empty(boolean타입) : 리스트가 비어있가?
  • 참고로 페이지 시작은 0부터 시작이다!

Slice, Page, List 차이점은?

Slice<User> findByLastname(String lastname, Pageable pageable);

Page<User> findByLastname(String lastname, Pageable pageable);

List<User> findByLastname(String lastname, Pageable pageable);

List<User> findByLastname(String lastname, Sort sort);

Slice

  • Slice는 Page에서 카운트 쿼리에 많은 비용이 발생하는 경우에 Slice를 사용하면 된다.
  • Slice는 다음 Slice가 존재하는지 여부만 알기 때문에 전체 데이터의 셋의 크기가 큰 경우에는 Slice를 사용하는 것이 성능상 유리하다.
  • 무한스크롤에 적합하다.

Page

  • page는 사용 가능한 데이터의 총 개수 및 전체 페이지 수를 알 수 있다.
  • 총 개수를 알아내기 위해 추가적으로 카운트 쿼리가 실행된다.
  • 기본적으로 카운트 쿼리는 실제로 실행되는 쿼리에서 파생된다.

List

  • Pageable을 통해서 정렬을 할 수 있지만, 정렬만 하는 경우 Sort를 사용하는 것이 좋다.
  • 결과를 단순히 List로 받을 수 있다.
  • 이 경우 Page 인스턴스를 생성하기 위한 메타데이터가 생성되지 않기 때문에 카운트 쿼리가 실행되지 않는다.
  • 단순히 주어진 범위내의 엔티티를 검색하기 위한 쿼리만 실행된다.

참고 블로그

참고자료

profile
어떠한 가치를 창출할 수 있을까를 고민하는 개발자. 주로 Spring으로 개발해요. https://comclothing.tistory.com/ 👈🏻티스토리 블로그로 이전 완료

1개의 댓글

comment-user-thumbnail
2023년 3월 8일

잘보고 갑니다! 레퍼런스도 정성스럽게 작성해주셔서 많은 도움 되었습니다!

답글 달기