게시판 페이징 구현

mynameisjaehoon·2024년 2월 5일
0

주말동안 게시판을 만들어보면서 게시글 페이징을 어떻게 구현했는지 설명한다.

개발환경

설명에 앞서 필자는

  • Spring boot
  • JPA
  • Thymeleaf

환경에서 개발을 진행하였다. API로 데이터를 반환하는 것이 아니기 때문에 SSR을 진행할 때 어떻게 페이징을 적용시켰는지 위주로 설명하겠다.

개발과정

데이터 가져오기

컨트롤러에서 Pageable 파라미터를 받고 URL로 페이징정보를 전달받는다. 다만 처음 게시물들을 조회하는 URL은 page, size 쿼리 파라미터가 없기 때문에 pageable에 전달되는 페이지 사이즈는 기본 페이지 사이즈인 20 이였다.
나는 페이지 사이즈의 기본값을 10으로 가져가고 싶었기 때문에 application.yml에서 spring.data.web.pageable.default-page.size 값을 10으로 설정해 기본 페이지 크기를 10으로 만들었다.

컨트롤러에서 전달받은 pageable을 레포지토리까지 전달하면서 페이징 검색에 사용한다. DB를 조회하는데는, JPA와 QueryDsl을 사용하였다.

@Override
public List<PostListInfoDto> fetchPosts(Pageable pageable) {
    return queryFactory
        .select(
            Projections.fields(
                PostListInfoDto.class,
                post.postId,
                post.title,
                member.name,
                post.createdDate,
                post.viewCount
            )
        ).from(post)
        .leftJoin(post.member, member)
        .orderBy(post.createdDate.desc())
        .offset(pageable.getOffset())
        .limit(pageable.getPageSize())
        .fetch();
}

게시글 정보를 가져올 때는 게시글 작성자도 함께 표시해주어야 하기 때문에 연관관계에 있는 멤버 정보도 함께 조회해야 한다. 따라서 leftJoin을 사용해주었고, offset과 limit를 통해서 페이징을 처리해주었다.

그리고 전체 게시글의 갯수도 쿼리로 조회해주었다. 여기서 얻어온 갯수 정보는 페이지 번호를 계산하는데 쓰인다.

@Override
public Long fetchPostsCount() {
    return queryFactory
            .select(post.count())
            .from(post)
            .fetchOne();
}

게시판에 필요한 정보를 API로 전달하고 클라이언트측에서 렌더링하는 프로젝트가 아니라, 필요한 데이터를 Model에 담고 타임리프를 사용해서 서버측에서 동적으로 렌더링하는 프로젝트였기 때문에 게시글에 대한 정보, 그리고 페이지를 계산하는데 필요한 정보를 Model에 담아 전달해야한다.
나는 페이지를 계산하는데 필요한 정보를 PageDto 객체에 담았다. PageDto는 현재 페이지(number), 한 페이지의 사이즈(size) 그리고 전체 게시글의 수(total)정보를 담고 있다.

public class PageDto {
    private int number;
    private int size;
    private Long total;
}

최종적으로 Model에 담기는 정보는 게시글들의 정보와 페이지의 정보가 모두 담긴 PostListResponseDto 라는 객체이다.

public class PostListResponseDto {
    private List<PostListInfoDto> info;
    private PageDto page;
}

이렇게 만들어진 PostListResponseDto 객체는 Model에 담겨 View계층으로 전달된다.

PostListResponseDto result = postService.fetchPosts(pageable);
model.addAttribute("posts", result);

페이징 로직

페이지 계산

페이징을 표현할 때 페이지 번호 표시의 가운데에 현재표시가 오도록 하고, 최소 현재 페이지의 -2 최대 현재페이지의 +2 까지 화면에 표현하였다. 예를 들어서 현재 페이지가 3이라면 1,2,3,4,5 가 표시되는 것이다.

<li class="page-item" aria-current="page" th:if="${posts.page.number - 2 >= 1}">
    <a class="page-link"  th:text="${posts.page.number - 2}" th:href="|/post?page=${posts.page.number - 3}&size=10|">1</a>
</li>

최소페이지를 계산하는 것을 어렵지 않았다. 현재 페이지에서 1, 2만큼 뺀 다음 1보다 크거나 같을 때만 표시하면 된다.
페이지 번호가 표시될 지는 th:if 의 조건을 만족하는 것으로 결정된다. th:if 내의 표현식이 true이면 해당 블록이 렌더링되고, false라면 렌더링하지 않는다.

링크는 th:href를 사용해서 href 속성을 대체해준다. 링크를 보면 쿼리스트링의 page값을 1더 빼주고있는데 그 이유는 pageable이 페이지를 0부터 세기 때문이다. 내가 모델에 전달한 페이지 정보는 페이지를 1부터 세는 것으로 저장했기 때문에 1을 더 빼주어야 제대로 동작한다.

현재 페이지보다 큰 페이지 값의 유무는 전체 게시물의 갯수를 이용해서 계산한다.

(다음 페이지 넘버 - 1) * (페이지 사이즈) + 1 <= (전체 게시글 갯수)

이 조건을 만족할 때 렌더링한다. 이 때 페이지 넘버는 1을 시작으로 할 때를 기준으로 계산한다.

맨 앞으로 이동하기

맨앞으로 이동하는 로직은 단순히 첫번째 페이지를 요청하면 된다.

맨 뒤로 이동하기

맨 뒤로 이동할 때의 페이지 계산은 두가지 경우가 있다.
1. 전체 데이터(게시글) 갯수가 페이지 사이즈와 나누어 떨어질 때
2. 전체 데이터 수와 페이지 사이즈가 나누어 떨어지지 않을 때

전체 데이터 수와 페이지 사이즈가 나누어 떨어진다면 마지막 페이지 번호는 (전체 데이터 수) / (페이지 사이즈) - 1 이 되고, 나누어 떨어지지 않는다면 마지막 페이지 번호는 (전체 데이터 수) / (페이지 사이즈) 가 된다.

0개의 댓글