[JPA] 게시판 프로젝트 - 게시판 페이징 구현 (9)

2025. 2. 15. 12:05·프로젝트/Board 프로젝트

 

이전 개발 보러 가기

https://taetae99.tistory.com/39

 

[Board] 벡앤드 토이 프로젝트 - 게시글 수정, 삭제 기능 개발 (8)

이전 개발 보러 가기https://taetae99.tistory.com/38 [Board] 벡엔드 토이 프로젝트 - 게시글 생성 기능 구현 (7)이전 개발 단계 보러 가기https://taetae99.tistory.com/36 벡엔드 토이 프로젝트(게시판) - MemberContro

taetae99.tistory.com

 

목차

     


     

    0. 페이징의 목적

    게시글이 누적되어 데이터가 많아진다면

    한 페이지에 모든 게시글을 불러오는 경우 매번 많은 양의 데이터를 조회해야 하므로 성능과 사용자 경험이 저하될 수 있다.

    페이징을 구현하여 필요한 데이터만 불러와 성능이 향상되고 사용자는 효율적으로 정보를 탐색할 수 있을 것이다.

     

     

    💡 페이징 요구사항

    1. 한 페이지에 10개의 게시글이 표시되도록 한다.
    2. 최대 5개의 페이지 버튼만 화면에 표시되도록 한다.
    3. 페이지 버튼 앞뒤로 '이전'과 '다음' 버튼이 표시되도록 한다.
    4. 첫 번째 페이지에서는 '이전' 버튼이 비활성화되고, 마지막 페이지에서는 '다음' 버튼이 비활성화되도록 한다. 

     

     

     

     

    아래는 페이징이 구현된 모습이다. 임의의 게시글을 101개 작성했다.

    메인화면
    메인화면

     

     

     


     

     

    1. Repository

     

    public Page<Post> findPostsWithPaging(Pageable pageable) {
            List<Post> posts = em.createQuery("select p from Post p order by p.id desc", Post.class)
                    .setFirstResult((int) pageable.getOffset())
                    .setMaxResults(pageable.getPageSize())
                    .getResultList();
    
            long totalCount = em.createQuery("select count(p) from Post p", Long.class).getSingleResult();
    
            return new PageImpl<>(posts, pageable, totalCount);
        }

     

    Repository에서 JPQL을 작성하여 게시글을 검색한다.

    • id를 기준으로 내림차순으로 정렬한다. 
    • setFirstResult는 검색할 첫 번째 결과의 위치를 지정한다.
    • pageable.getOffset은 현재 페이지에서 조회를 시작할 데이터의 위치를 반환한다.
      • 예를 들어 한 페이지에 10개씩 출력하고 현재 페이지가 5페이지라면 5 * 10 = 50으로 50번째 데이터부터 조회한다.
    • setMaxResult는 반환할 결과의 최대 개수를 지정한다.
    • pageable.getPageSize()는 한 페이지에 표시할 데이터 개수를 반환한다.
    • totalCount는 전체 페이지 수를 계산하는 데 필요하므로 DB에서 전체 게시글 수를 조회하여 저장한다.

     

    공식문서1
    공식문서2

     

     

     

    반환 시에는 PageImpl을 사용하여 조회된 List<Post>를 Page<Post>로 변환한다.

    Page<T>는 Spring Data JPA에서 페이징 처리된 데이터를 담는 인터페이스이다.

    PageImpl<T>는 Page<T>의 구현체로 List<T>와 페이징 정보를 함께 받아 Page<T> 객체로 변환하여 반환한다.

    PageImpl
    pageImpl에 total 값을 함께 저장

     

     

    2. Service

     

    public Page<Post> findPagePosts(Pageable pageable) {
            return postRepository.findPostsWithPaging(pageable);
        }

     

     

    pageable을 인자로 받아 페이징 처리된 게시글 데이터를 반환한다.

     

     

     

     

     

    3. Controller

     

    @GetMapping({"/", "/board"})
        public String home(@AuthenticationPrincipal MemberDetail memberDetail, Model model,
                           @PageableDefault(page = 0,size = 10) Pageable pageable) {
    
            if (memberDetail != null) {
                model.addAttribute("nickname", memberDetail.getNickname());
            }
    
            Page<Post> posts = postService.findPagePosts(pageable);
    
            int limit = 5;
            int startPage = getStartPage(pageable, limit);
            int endPage = getEndPage(startPage, limit, posts.getTotalPages());
    
            PageInfoDto pageInfo = PageInfoDto.createPageInfo(startPage, endPage, pageable.getPageNumber(), posts.getTotalPages(), posts);
    
            model.addAttribute("pageInfo", pageInfo);
    
            return "board";
        }
        
        private static int getEndPage(int startPage, int limit, int totalPages) {
            return Math.min(startPage + limit - 1, totalPages);
        }
    
        private static int getStartPage(Pageable pageable, int limit) {
            return ((pageable.getPageNumber() / limit) * limit) + 1;
        }

     

    컨트롤러에서는 기존의 메인화면에 게시글 목록을 불러오는 메서드를 수정하였다.

    • @PageableDefault를 사용하여 Pageable객체에 값을 설정한다. 
    • limit는 화면에 표시되는 페이지 버튼의 개수를 설정하는 변수이다.
      • 예를 들어 limit가 5인 경우 1번~5번 페이지까지 표시하고 6번 페이지를 클릭하면 6번~10번까지 표시한다.
    • startPage는 현재 페이지를 기준으로 화면에 표시되는 페이지 버튼의 시작 값을 담은 변수이다.
      • 예를 들어 네비게이션에 6,7,8,9,10 페이지 버튼이 존재한다면 startPage의 값은 6이다.
    • endPage는 startPage에서 계산된 페이지 범위 내에서의 끝 값이다.
      • 예를 들어 네비게이션에 6,7,8,9,10 페이지 버튼이 존재한다면 startPage는 6이고 endPage는 10이다.

     

    @PageableDefult

     

    @PageableDefault 어노테이션은 Pageable 객체에 값을 설정하는 데 사용된다.

    • page 속성은 조회를 시작하는 페이지 번호를 설정한다. 기본값은 0이다.
      • UI에서는 페이지 번호를 1부터 사용하지만 Spring에서 Page는 기본값으로 0으로 설정되어 있기 때문에 주의하여 사용해야 한다.
    • size는 한 페이지에 표시할 데이터의 개수를 설정한다. 기본값은 10이다.
    • sort를 사용해 데이터의 정렬 기준을 설정할 수 있다.

     

     

    4. Thymleaf

     

    <nav aria-label="Page navigation">
            <ul class="pagination justify-content-center">
    
                <li class="page-item" th:classappend="${pageInfo.currentPage == 0} ? 'disabled'">
                    <a class="page-link" th:href="@{/board(page=${(pageInfo.currentPage - 1)})}">이전</a>
                </li>
    
                <li class="page-item" th:if="${pageInfo.totalPages>0}" th:each="pageNum : ${#numbers.sequence(pageInfo.startPage,pageInfo.endPage)}"
                    th:classappend="${pageNum == (pageInfo.currentPage+1)} ? 'active'">
                    <a class="page-link" th:href="@{/board(page=${(pageNum - 1)})}" th:text="${pageNum}"></a>
                </li>
    
    
                <li class="page-item" th:classappend="${(pageInfo.currentPage+1) >= pageInfo.totalPages} ? 'disabled'">
                    <a class="page-link" th:href="@{/board(page=${pageInfo.currentPage + 1})}">다음</a>
                </li>
            </ul>
        </nav>

     

    @PageableDefault에서 page 값을 0으로 설정했지만 네비게이션에 표시되는 페이지 번호는 1부터 시작하도록 설정했다.

     

    이로 인해 실제 페이지 값(@PageableDefault의 page)과 네비게이션에 표시되는 번호 사이에 모순이 발생한다.

     

    이를 위해 currentPage와 pageNum을 다룰 때 값을 1씩 증감하여 처리한다.

    또한 페이지 네비게이션의 '이전' 버튼은 현재 페이지가 0일 때 비활성화되고 '다음' 버튼은 마지막 페이지에서 비활성화된다.

     

     

    url
    @PageableDefault의 page=0 설정으로 page는 0에서 시작한다.
    메인화면
    UI에서는 페이지 번호를 1부터사용한다.

     

     

     

    5. PageInfoDto

     

    @Getter
    public class PageInfoDto {
        private int startPage;
        private int endPage;
        private int currentPage;
        private int totalPages;
        private Page<Post> posts;
    
        protected PageInfoDto() {
        }
        private PageInfoDto(int startPage, int endPage, int currentPage, int totalPages, Page<Post> posts) {
            this.startPage = startPage;
            this.endPage = endPage;
            this.currentPage = currentPage;
            this.totalPages = totalPages;
            this.posts = posts;
        }
    
        public static PageInfoDto createPageInfo(int startPage, int endPage, int currentPage, int totalPages, Page<Post> posts) {
            return new PageInfoDto(startPage, endPage, currentPage, totalPages, posts);
        }
    
    }

     

    페이징 처리된 데이터를 화면에 전달하기 위해 Dto를 생성했다.

    • startPage, endPage는 시작과 끝 페이지 정보를 담고 있다.
    • currentPage는 현재 페이지 정보를 담고 있다.
    • totalPages는 전체 페이지 수이다.
    • posts는 현재 페이지에 해당하는 게시글을 담고 있다.

     

     

     

    6. 실행 모습 

     

    두 가지 경우에 대해서 실행 모습을 보여주고자 한다.

     

     

    6-1. 게시글이 101개 존재할 때 

     

    게시글이 101개 존재한다면 한 페이지에 10개의 게시물이 존재하므로 총 11개의 페이지가 생긴다.

     

    101개의 게시글

     

    1페이지에서는 '이전' 버튼이 비활성화된다.

     

    6-10 버튼 활성화

     

    6페이지로 넘어가면 6-10까지의 버튼이 보인다.

     

    다음 버튼 비활성화

     

    마지막 페이지에 도달하면 '다음' 버튼이 비활성화된다.

     

     

     

    6-2. 게시글이 한 개 존재할 때

     

    게시글이 한 개 존재한다면 페이지는 한 개 존재한다.

     

    게시글 하나일때

     

    게시글을 삭제하면 페이지가 존재하지 않는다.

     

    게시글 제로

     

     

     

     

     

     

     

     

    반응형

    '프로젝트 > Board 프로젝트' 카테고리의 다른 글

    [JPA] 게시판 프로젝트 - 마이그레이션 : H2에서 MySQL (11)  (1) 2025.02.28
    [JPA] 게시판 프로젝트 - 비동기 없이 댓글 수정 및 댓글 생성, 삭제 구현 (10)  (0) 2025.02.24
    [JPA] 게시판 프로젝트 - 게시글 수정, 삭제 기능 개발 (8)  (1) 2025.02.09
    [JPA] 게시판 프로젝트 - 게시글 생성 기능 구현 (7)  (4) 2025.02.03
    '프로젝트/Board 프로젝트' 카테고리의 다른 글
    • [JPA] 게시판 프로젝트 - 마이그레이션 : H2에서 MySQL (11)
    • [JPA] 게시판 프로젝트 - 비동기 없이 댓글 수정 및 댓글 생성, 삭제 구현 (10)
    • [JPA] 게시판 프로젝트 - 게시글 수정, 삭제 기능 개발 (8)
    • [JPA] 게시판 프로젝트 - 게시글 생성 기능 구현 (7)
    taetae99
    taetae99
    우직하게 개발하기
      반응형
    • taetae99
      코드 대장간
      taetae99
    • 전체
      오늘
      어제
      • 분류 전체보기
        • Teck Stack
          • Java
          • Spring
          • DB
          • Redis
          • SpringSecurity
          • Docker
          • HTML
          • AWS
        • 우아한테크코스
        • CS & Architecture
          • DDD
          • CS
          • 디자인 패턴
        • 트러블 슈팅
        • 알고리즘
          • 프로그래머스
          • 백준
        • 프로젝트
          • Board 프로젝트
        • 기타
        • 대회 및 후기
    • 인기 글

    • hELLO· Designed By정상우.v4.10.3
    taetae99
    [JPA] 게시판 프로젝트 - 게시판 페이징 구현 (9)
    상단으로

    티스토리툴바