[BEInternship] Pagenation 적용하기

junghan·2023년 8월 15일
0

BE 인턴십

목록 보기
5/9
post-thumbnail

Pagination이란?

웹 사이트를 이용하며 게시판을 둘러볼 때, 게시글 목록 하단에 게시글을 일정 수로 나누어 페이지화 시켜놓은걸 볼 수 있는데 이를 Pagination(페이지네이션) 이라고 합니다.

💡 직접 구현해본 pagination 예제


직관적으로 게시글을 검색할 수 있기 때문에 커뮤니티뿐 아니라 많은 게시글을 보여주는 곳에서 구현 및 사용하고 있습니다.



Pagination 구현이론

pagination을 구현하기 위해서는 현재 페이지번호각 페이지별 보여질 내용의 수페이지에 들어갈 컨텐츠전체 페이지 개수전체 데이터의 수가 필요합니다.

이 데이터들을 얻으려면 최소 2번의 API 요청(데이터 요청, 데이터 카운트 콜)을 통해 데이터를 가져와야 합니다. 2번의 요청이 싫다면, 한번의 요청으로 모든 데이터를 가져와야 하는데 이 경우에는 데이터가 매우 많을 경우에 성능 이슈가 생길 수 있습니다.

Spring 프레임워크에서는 이러한 고민을 해결 할 수 있는 방법 중 하나가, Spring-Data-JPA 라이브러리의 Page와 Pageable을 이용하는 것입니다.

Spring Data Jpa의 제공 메서드

Pageable

Pageable은 Spring JPA에서 DB 쿼리에 쉽고 유연하게 limit 쿼리를 사용할 수 있게 해준다. 특히 JPA를 사용할 때, 자동으로 Pageable 타입의 변수를 넘겨주면 JPA가 DB에 접근해 데이터를 가져올 때 자동으로 limit 조건을 붙여 데이터를 가져온다.

@PageableDefault

Spring의 Controller의 메서드 파라미터에 해당 어노테이션이 선언 된 Pageable 타입 파라미터를 선언하게 되면, API 요청 시 Pageable 객체에 대한 파라미터를 넘겨주지 않아도 자동으로 기본값을 가진 Pageable 타입 파라미터를 제공해준다.

이 어노테이션을 사용하지 않는다면 HandlerMethodArgumentResolver 같은 인터페이스를 구현한 클래스를 만들어 조건에 맞게 매핑을 해줘야 한다.

Page

Pageble을 파라미터로하여 가져온 결과물은 Page 형태로 반환 되며, Page를 사용한다면 대부분 다수의 row를 가져오기 때문에 Page<List>의 형태로 반환을 한다. 이 페이지 객체에는 Pagination을 구현할 때 사용하면 좋은 메서드가 있으며 이는 다음과 같다.

  • getTotalElements()
    쿼리 결과물의 전체 데이터 개수이다. 즉, Pageable에 의해 limit키워드가 조건으로 들어가지 않는 쿼리 결과의 수 인데, 주의해야 할 점은 쿼리 결과의 갯수만 가져오지 전체 데이터를 가져오지 않는다는 점이다.
    이 메서드는 게시판 기능 사용자에게 전체 데이터 개수를 알려주는 등에 사용하기 좋다.

  • getTotalPages()
    쿼리를 통해 가져온 요소들을 size크기에 맞춰 페이징하였을 때 나오는 총 페이지의 갯수이다.
    이를 활용해 쉽게 페이지 버튼의 생성이 가능하다.

  • getSize()
    쿼리를 수행한 전체 데이터에 대해 일정 수 만큼 나눠 페이지를 구성하는데, 이 일정 수의 크기이다.

  • getNumber()
    요소를 가져온 페이지의 번호를 의미한다.

  • getNumberOfElements()
    페이지에 존재하는 요소의 개수이다. 최대 size의 수 만큼 나올 수 있다.



구현

repository

public interface PostsRepository extends JpaRepository<Posts, Long> {

    @Query("SELECT p FROM Posts p ORDER BY p.id DESC")
    List<Posts> findAllDesc();

    Page<Posts> findByDeletedOrderByIdDesc(boolean nonDeleted, Pageable pagaeble);
}

JPARepository를 상속받아 repository를 구현.

findByDeletedOrderByIdDesc(boolean nonDeleted, Pageable pagaeble);

💬 삭제된 데이터를 제외하고 Id를 기준으로 내림차순으로(최신순) 모든 정보를 가져온다.

매개변수로 Pageable 타입의 객체를 넘겨주면, 객체의 정보를 읽고 page 조건을 설정하여 데이터를 가져온다.

Service

    public Page<Posts> findPosts(int page, int size) {
        PageRequest pageRequest = PageRequest.of(page, size);
        return postsRepository.findByDeletedOrderByIdDesc(false, pageRequest);
    }

page 개수와 size(page별 데이터 개수)를 매개변수로 받아 PageRequest 객체를 생성하여 repository로 반환한다.

PageRequest는 Pageable을 구현하며, Sort 객체를 매개변수로 받아 정렬조건을 설정할수도 있다.

Controller

    @GetMapping("api/v1/postslist")
    @Operation(summary = "게시물 목록", description = "게시물 목록 정보를 요청합니다.", tags = {"Posts"})
    public ResponseEntity getPostsList(@Positive @RequestParam int page, @Positive @RequestParam int size) {
        Page<Posts> postsPage = postsServiceImpl.findPosts(page-1, size);
        PageInfo pageInfo = new PageInfo(page, size, (int) postsPage.getTotalElements(), (int) postsPage.getTotalPages());

        List<Posts> posts = postsPage.getContent();
        List<PostsListResponse> postsListResponses = posts.stream().map(PostsListResponse::new).collect(Collectors.toList());

        return new ResponseEntity<>(new AllPageInfo(postsListResponses, pageInfo), HttpStatus.OK);

    }

page와 size를 query parameter로 받는다.

page information

PostsServiceImpl에서 반환한 Page객체(memberPage)에서 제공하는 페이지 개수, 페이지 당 데이터 개수, 총 데이터 수 등의 정보를 이용해 페이지 정보를 담고있는 PageInfo 객체를 생성했다. (page는 0부터 시작이기 때문에 page-1)

@Getter
@AllArgsConstructor
public class PageInfo {
    private  int page;
    private  int size;
    private  int totalElements;
    private  int totalPages;
}

Posts 반환 + dto로 변환

이후 Page객체의 getContent를 호출해 실제 데이터를 받아오고, Dto 객체로 변환한다.*

AllPageInfo

@Getter
public class AllPageInfo<T> {

    private T data;
    private PageInfo pageInfo;

    public AllPageInfo(T data, PageInfo pageInfo) {
        this.data = data;
        this.pageInfo = pageInfo;
    }

}

PostsListResponse

@Getter
public class PostsListResponse {

    private Long id;
    private String title;
    private String author;

    public PostsListResponse(Posts entity) {
        this.id = entity.getId();
        this.title = entity.getTitle();
        this.author = entity.getAuthor();
    }
}

전체 posts와 page정보를 함께 반환하는 별도의 Dto클래스(AllPageInfo) 생성 후, postsListResponses와 pageInfo 객체를 Dto로 반환한다.

응답

요청 URI 형식 : http://localhost:8081/api/v1/posts?page=1,size=20


https://velog.io/@albaneo0724/Spring-Pagination%EA%B3%BC-Page-%EA%B7%B8%EB%A6%AC%EA%B3%A0-Pageable
https://velog.io/@bagt/0704-Spring-Pagination-API-%ED%8E%98%EC%9D%B4%EC%A7%80%EB%84%A4%EC%9D%B4%EC%85%98

profile
42seoul, blockchain, web 3.0

0개의 댓글