본문 바로가기
게시판 만들기(Spring Boot, JPA)

게시판 만들기 03 - 게시글 페이징

by burpee 2022. 12. 8.

페이징 처리 하는 이유

여러 사이트를 돌아다니다 보면 아래 사진과 같은 걸 본 적이 분명히 있을 것이다.

인프런 커뮤니티 게시판 페이징

이런 페이징 처리는 많은 데이터들을 여러 페이지로 나눠서 제공하므로 데이터를 보는 사람들에게 효과적으로 보여주게 된다. 내가 지금 만들고 있는 게시판도 수백, 수천 개의 게시글이 생길 수도 있는데 이 게시글들을 효과적으로 보여주려면 페이징 처리를 하는 게 좋을 것이다.

 

JPA에서 페이징

나는 지금 MySQL을 사용하고 있는데 만약에 JPA를 사용하지 않았다면 limit, offset 키워드를 활용해서 페이징 처리가 가능한 쿼리를 날렸을 것이다. 하지만 JPA에서는 아주 간단한 페이징 처리 방법을 제공한다.

먼저 저번 포스팅에서 PostRepository를 생성할 때 JpaRepository라는 인터페이스를 상속받으면 기본적인 CRUD를 사용할 수 있다고 했다. 그중에 save, findById, delete를 사용해 봤는데, 이번 페이징 처리 때 사용할 메서드는 [ic] findAll [/ic]이라는 메서드이다. 이 메서드를 조금 살펴보면서 어떻게 사용하는지 알아본 후에 페이징 처리를 시작해야겠다.

 

JpaRepository

PostRepository가 상속받은 JpaRepository를 살펴보면 findAll 메소드를 찾을 수 있다. 파라미터가 없는 첫 번째 메서드의 기능을 추측해 보면 아마 모든 데이터를 가져와 List로 반환해 주는 것으로 보인다. 두 번째 Sort를 파라미터로 받는 메서드는 특정 조건으로 정렬을 한 후에 모든 데이터를 List로 반환해 주는 것으로 보인다. 이렇게 페이지를 나누지 않고 전체 데이터를 다 받으면 페이징은 어떻게 하나?라는 생각이 드는데 JpaRepository가 상속받은 PagingAndSortingRepository 확인해보자.

 

PagingAndSortingRepository

뭔가 찾은 것 같다. 두 번째 메서드를 확인해 보면 Pageable 객체를 파라미터로 받고 데이터를 Page라는 클래스로 감싸서 반환해 준다. 그렇다면 Page클래스는 무엇이고, Pageable 클래스는 무엇일까? 방금 JpaRepository의 상속 관계를 찾아본 것처럼 command + 클릭해서 들어가 보면 알 수 있다. Page 인터페이스는 반환받은 페이지의 리스트 데이터를 감싸는 인터페이스로 보이고, 해당 리스트의 여러 가지 정보들(현재 페이지, 현재 페이지의 콘텐츠 수, 전체 컨텐츠 수 등)을 가져올 수 있는 메서드들이 정의되어 있다. 그리고 Pageable 인터페이스는 페이징 데이터를 요청할때 사용된 조건들을 가져올 수 있는 메소드들이 정의되어 있다. 페이징 데이터를 요청할 때는 Pageable 인터페이스의 구현 클래스 중에 하나인 PageRequest를 사용하면 될 것 같다.

 

자 이제 게시글 페이징 처리를 해보자!

 

게시글 페이징 처리

PostService

PostService > getList

public ResponsePostListDto getList(RequestListDto requestDto) {
    PageRequest pageRequest = PageRequest.of(requestDto.getPage(), requestDto.getPageSize(), Sort.Direction.DESC, "postId");
    Page<Post> postList = postRepository.findAll(pageRequest);

    ResponsePostListDto responsePostListDto = ResponsePostListDto.builder()
            .totalCount((int) postList.getTotalElements())
            .page(postList.getNumber())
            .pageSize(postList.getSize())
            .postList(postList.stream().map(post -> ResponsePostDto.builder()
                    .postId(post.getPostId())
                    .title(post.getTitle())
                    .content(post.getContent())
                    .createdDate(post.getCreatedDate())
                    .build()).collect(Collectors.toList()))
            .build();

    return responsePostListDto;
}

 

먼저 PostService에 getList 메서드를 작성했다. 파라미터로 RequestListDto 클래스가 있는데 이 클래스 먼저 살펴보자.

 

RequestListDto

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class RequestListDto {

    private Integer page = 0;
    private Integer pageSize = 10;

    public Integer getPage() {
        page = page - 1;
        if (page < 0) {
            page = 0;
        }
        return page;
    }
}

 

이 클래스에는 페이징 요청을 할 때 필요한 요청할 페이지의 쪽수인 page, 한 페이지당 보여줄 게시글의 개수인 pageSize 필드를 가지고 있다. 혹시 page와 pageSize가 [ic] null [/ic]로 들어올 때를 대비하여 첫 페이지를 뜻하는 0과 10개의 게시글을 뜻하는 10으로 기본값을 초기화해주었다.

그리고 getPage 메서드는 조금 수정해서 작성했다. 저렇게 page에서 1을 뺀 값으로 데이터를 내어주는 이유는 클라이언트 쪽에서 요청하는 페이지 쪽수는 1부터 시작이지만, JPA에 페이징 요청을 날릴 때 사용하는 페이지의 쪽수는 0부터 시작이기 때문이다. 같은 이유로 page 변수의 초기값도 0인 것이다.

 

이제 PostRepository의 [ic] findAll [/ic] 메서드의 파라미터로 보낼 PageRequest를 만들어야 한다. 

PageRequest pageRequest = PageRequest.of(requestDto.getPage(), requestDto.getPageSize(), Sort.Direction.DESC, "postId");
Page<Post> postList = postRepository.findAll(pageRequest);

 

위와 같이 page, pageSize, 정렬 조건들을 포함해서 PageRequest 객체를 생성해 주고 findAll 메서드를 호출할 때 파라미터로 넣어주면 페이징 처리가 된 Post 엔티티를 받을 수 있다. 받아온 값은 클라이언트에게 전달해 줄 Dto로 적절하게 변환시켜서 Controller로 반환하면 Service 계층은 끝이다. Dto는 어떻게 생겼는지 살펴보자

 

ResponsePostListDto

@Getter
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class ResponsePostListDto extends ResponseListDto {

    private List<ResponsePostDto> postList;
}

 

ResponsePostListDto는 게시글 단건 조회에서 사용했던 ResponsePostDto의 리스트를 담고 있다. 이 리스트에는 한 페이지의 게시글 데이터가 담길 것이다. 그리고 ResponseListDto라는 클래스를 상속받는다.

 

ResponseListDto

@Getter
@SuperBuilder
@NoArgsConstructor
public class ResponseListDto {

    private Integer totalCount;
    private Integer page;
    private Integer pageSize;
}

 

이 클래스는 클라이언트가 페이징 처리를 할 때 사용하는 데이터인 전체 콘텐츠 수(totalCount), 현재 페이지(page), 한 페이지당 컨텐츠 수(pageSize)가 담겨있다. 이 필드들은 페이징 처리가 되어서 반환되는 Dto들에 거의 다 포함될 것이기 때문에 부모 클래스로 만들어 두었고, 따라서 페이징 처리가 된 Dto들은 이 클래스를 상속받으면서 재사용할 예정이다.

 

이제 PostController로 넘어가 보자.

 

PostController

@GetMapping
public ResponseEntity<ResponsePostListDto> getPostList(@ModelAttribute RequestListDto requestDto) {
    ResponsePostListDto responsePostListDto = postService.getList(requestDto);

    return ResponseEntity.ok(responsePostListDto);
}

 

PostController는 클라이언트가 쿼리 스트링으로 보낸 파라미터들을 RequestListDto에 담고 postService의 getList 메서드를 호출해 주면 된다. 그리고 반환받은 ResponseListDto를 200 OK 상태와 함께 클라이언트에 내려주면 된다.

 

이제 게시글 기본적인 게시글 CRUD를 다 마무리한 것 같다. 다음 포스팅에는 테스트 코드를 작성하면서 현재까지 만든 게시글 CRUD API를 점검해보려고 한다.


소스코드

https://github.com/cheoljin408/simple-board-api

 

이전 글

2022.11.25 - [게시판 만들기(Spring Boot, JPA)] - 게시판 만들기 하는 이유

2022.11.30 - [게시판 만들기(Spring Boot, JPA)] - 게시판 만들기 01 - 게시글 Entity 만들기

2022.12.02 - [게시판 만들기(Spring Boot, JPA)] - 게시판 만들기 02 - 게시글 CRUD

 

댓글