Spring Boot

[게시판 만들기 (8)] 게시글 페이징_페이징 화면 처리

서윤-정 2024. 1. 14. 18:05

 

 

index.html의 pagingReq 함수를 추가한다.

 

[ index.html ]

<button onclick="saveReq()">글작성</button>
<a href="/board/save">글작성(링크)</a>
<button onclick="listReq()">글목록</button>
<button onclick="pagingReq()">페이징목록</button>
</body>
<script>
    //  function saveReq() {
    //
    //  }

    const saveReq = () => {
        location.href = "/board/save";
    }
    const listReq = () => {
        location.href = "/board/";
    }
    const pagingReq = () => {
        location.href = "/board/paging";
    }

 

 

 

 

 

 

 

 

 

 

 

 

 

BoardController에 paging 메서드를 추가한다.

 

[ BoardController ]

    @GetMapping("/paging")
    public String paging(@PageableDefault(page = 1) Pageable pageable, Model model) {
//        pageable.getPageNumber();
        Page<BoardDTO> boardList = boardService.paging(pageable);
        int blockLimit = 3;
        int startPage = (((int)(Math.ceil((double)pageable.getPageNumber() / blockLimit))) - 1) * blockLimit + 1; // 1 4 7 10 ~~
        int endPage = ((startPage + blockLimit - 1) < boardList.getTotalPages()) ? startPage + blockLimit - 1 : boardList.getTotalPages();

        // page 갯수 20개
        // 현재 사용자가 3페이지
        // 1 2 3
        // 현재 사용자가 7페이지
        // 7 8 9
        // 보여지는 페이지 갯수 3개
        // 총 페이지 갯수 8개

        model.addAttribute("boardList", boardList);
        model.addAttribute("startPage", startPage);
        model.addAttribute("endPage", endPage);
        return "paging";

    }

 

@PageableDefault(page = 1) Pageable pageable

이 어노테이션은 spring data에서 제공하는 페이징 처리를 위한 어노테이션이다.

page 매개변수는 현재 페이지를 나타내며, 기본값은 1이다.

pageable 객체는 페이징 정보를 담고 있다.

 

Page<BoardDTO> boardList = boardService.paging(pageable);

paging 메서드를 호출하여 현재 페이지에 해당하는 게시글 목록을 가져온다.

이 메서드는 페이징 처리된 결과를 Page 객체로 변환한다.

 

페이징 처리 및 모델에 속성 추가

페이징 계산: 현재 페이지를 기준으로 시작 페이지와 끝 페이지를 계산한다.

blockLimit 를 이용하여 한 블록에 표시되는 페이지 수를 지정하고,

현재 페이지를 기준으로 시작 페이지와 끝 페이지를 계산한다.

 

int startPage: 페이징 블록의 시작 페이지를 계산한다.

pageable.getPageNumber(): 현재 페이지의 번호를 가져온다.

(double)pageable.getPageNumber() / blockLimit: 현재 페이지를 블록 단위로 나눈다.

예를 들어, blockLimit가 3이면, 1페이지, 2페이지, 3페이지는 같은 블록에 속한다. 

Math.ceil((double)pageable.getPageNumber() / blockLimit)

: 나눈 결과를 올림하여 현재 페이지가 속한 블록의 번호를 얻는다.

-1: 블록 번호에서 1을 때서 시작 페이지가 될 페이지 블록의 첫 번째 페이지를 계산한다.

(예를 들어, 사용자가 3페이지를 요청하면, 내부적으로는 2페이지(0부터 시작하는 인덱스에서 2번째 페이지)에 해당한다.

따라서 블록 번호를 계산할 때 1을 빼서 사용자에게 표시되는 페이지 번호와 내부적인 인덱스를 맞춘다.)

* blockLimit + 1: 블록 내의 첫 번째 페이지 번호를 계산한다.

(예를 들어, 페이지 블록 크기(blockLimit)가 3이고 현재 페이지가 7이라고 가정한다.

블록 번호 계산: 'Math.ceil((double) 7 / 3) - 1' 은 2가 된다. (7이 속한 블록은 3개 페이지로 이루어진 두 번째 블록)

첫 번째 페이지 번호 계산: '(2 * 3) - 1' 은 5가 된다. (현재 블록의 첫 번째 페이지는 5)

 

 

int endPage: 페이징 블록의 끝 페이지를 계산한다.

(startPage + blockLimit - 1): 시작 페이지에서 블록 크기를 더하고 1을 빼서 블록 내의 마지막 페이지를 계산한다.

< boardList.getTotalPages()): 계산한 마지막 페이지가 총 페이지 수보다 작을 경우에만 해당 값을 사용한다.

? startPage + blockLimit - 1 : boardList.getTotalPages();: 조건 연산자를 사용하여 마지막 페이지를 정한다.

만약 계산한 마지막 페이지가 총 페이지 수보다 크다면, 총 페이지 수를 사용한다. 

(예를 들어, 페이지 블록 크기가 3이고 현재 페이지 블록의 첫 번째 페이지가 4일 때, (4+3-1)은 6이 되어

마지막 페이지가 6까지 표시된다.

그러나 전체 페이지 수가 5라면, 'boardList.getTotalPages()' 값인 5로 대체하여 5까지만 표시하도록 하는 것이다.)

 

 

모델에 속성 추가: model.addAttribute 를 사용하여 템블릿(뷰)에서 사용할 속성을 추가한다.

boardList에는 페이징 처리된 게시글 목록이, 

startPage와 endPage에는 현재 블록의 시작 페이지와 끝 페이지가 추가된다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BoardService 클래스에 paging 메서드를 추가한다.

spring data jpa를 사용하여 페이지별로 게시글을 조회하는 메서드이다.

 

[ BoardService ]

public Page<BoardDTO> paging(Pageable pageable) {
    int page = pageable.getPageNumber() - 1;
    int pageLimit = 3; // 한 페이지에 보여줄 글 갯수
    // 한페이지당 3개씩 글을 보여주고 정렬 기준은 id 기준으로 내림차순 정렬
    // page 위치에 있는 값은 0부터 시작
    Page<BoardEntity> boardEntities =
            boardRepository.findAll(PageRequest.of(page, pageLimit, Sort.by(Sort.Direction.DESC, "id")));

    System.out.println("boardEntities.getContent() = " + boardEntities.getContent()); // 요청 페이지에 해당하는 글
    System.out.println("boardEntities.getTotalElements() = " + boardEntities.getTotalElements()); // 전체 글갯수
    System.out.println("boardEntities.getNumber() = " + boardEntities.getNumber()); // DB로 요청한 페이지 번호
    System.out.println("boardEntities.getTotalPages() = " + boardEntities.getTotalPages()); // 전체 페이지 갯수
    System.out.println("boardEntities.getSize() = " + boardEntities.getSize()); // 한 페이지에 보여지는 글 갯수
    System.out.println("boardEntities.hasPrevious() = " + boardEntities.hasPrevious()); // 이전 페이지 존재 여부
    System.out.println("boardEntities.isFirst() = " + boardEntities.isFirst()); // 첫 페이지 여부
    System.out.println("boardEntities.isLast() = " + boardEntities.isLast()); // 마지막 페이지 여부

    // 목록: id, writer, title, hits, createdTime
    Page<BoardDTO> boardDTOS = boardEntities.map(board -> new BoardDTO(board.getId(), board.getBoardWriter(), board.getBoardTitle(), board.getBoardHits(), board.getCreatedTime()));
    return boardDTOS;
}

 

int page = pageable.getPageNumber() - 1;

pageable에서 가져온 현재 페이지 번호를 1 감소시킨다.

spring data jpa에서는 페이지 번호가 0부터 시작하기 때문에,

실제로는 현재 페이지 -1이 실제로 요청할 페이지 번호가 된다.

 

int pageLimit = 3

한 페이지에 보여줄 글의 개수를 정의한다.

 

Page<BoardEntity> boardEntities = boardRepository.findAll(PageRequest.of(page, pageLimit, Sort.by(Sort.Direction.DESC, "id")));

spring data jpa의 findAll 메서드를 사용하여 게시글을 페이지별로 조회한다.

현재 페이지, 페이지 크기, id 기준으로 내림차순 정렬하는 조건을 설정한다.

 

boardEntities.getContent()); // 요청 페이지에 해당하는 글

boardEntities.getTotalElements()); // 전체 글갯수

boardEntities.getNumber()); // DB로 요청한 페이지 번호
boardEntities.getTotalPages()); // 전체 페이지 갯수
boardEntities.getSize()); // 한 페이지에 보여지는 글 갯수
boardEntities.hasPrevious()); // 이전 페이지 존재 여부
boardEntities.isFirst()); // 첫 페이지 여부
boardEntities.isLast()); // 마지막 페이지 여부

 

Page<BoardDTO> boardDTOS = boardEntities.map

BoardEntity를 BoardDTO로 변환한다. 

여기서는 필요한 필드만 선택하여 BoardDTO 객체를 생성한다.

 

 

 

 

 

 

 

 

 

템플릿 패키지에 paging.html 파일을 생성한다.

 

[ paging.html ]

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<button onclick="saveReq()">글작성</button>

<table>
    <tr>
        <th>id</th>
        <th>title</th>
        <th>writer</th>
        <th>date</th>
        <th>hits</th>
    </tr>
    <tr th:each="board: ${boardList}">
        <td th:text="${board.id}"></td>
        <td><a th:href="@{|/board/${board.id}|(page=${boardList.number + 1})}" th:text="${board.boardTitle}"></a></td>
        <td th:text="${board.boardWriter}"></td>
        <td th:text="*{#temporals.format(board.boardCreatedTime, 'yyyy-MM-dd HH:mm:ss')}"></td>
        <td th:text="${board.boardHits}"></td>
    </tr>
</table>
<!-- 첫번째 페이지로 이동 -->
<!-- /board/paging?page=1 -->
<a th:href="@{/board/paging(page=1)}">First</a>
<!-- 이전 링크 활성화 비활성화 -->
<!-- boardList.getNumber() 사용자:2페이지 getNumber()=1 -->
<a th:href="${boardList.first} ? '#' : @{/board/paging(page=${boardList.number})}">prev</a>

<!-- 페이지 번호 링크(현재 페이지는 숫자만)
        for(int page=startPage; page<=endPage; page++)-->
<span th:each="page: ${#numbers.sequence(startPage, endPage)}">
<!-- 현재페이지는 링크 없이 숫자만 -->
    <span th:if="${page == boardList.number + 1}" th:text="${page}"></span>
    <!-- 현재페이지 번호가 아닌 다른 페이지번호에는 링크를 보여줌 -->
    <span th:unless="${page == boardList.number + 1}">
        <a th:href="@{/board/paging(page=${page})}" th:text="${page}"></a>
    </span>
</span>

<!-- 다음 링크 활성화 비활성화
    사용자: 2페이지, getNumber: 1, 3페이지-->
<a th:href="${boardList.last} ? '#' : @{/board/paging(page=${boardList.number + 2})}">next</a>
<!-- 마지막 페이지로 이동 -->
<a th:href="@{/board/paging(page=${boardList.totalPages})}">Last</a>

</body>
<script>
    const saveReq = () => {
        location.href = "/board/save";
    }

</script>
</html>

 

<a th:href="${boardList.first} ? '#' : @{/board/paging(page=${boardList.number})}">prev</a>

${boardList.first}

boardList는 페이지네이션에 사용되는 spring data의 'Page' 객체이다.

'first' 속성은 현재 페이지가 첫 페이지인지 여부를 나타내는 속성이다.

만약 현재 페이지가 첫 페이지이면 'true'를, 그렇지 않으면 'false'를 반환한다.

 

? '#' : @{/board/paging(page=${boardList.number})}

삼항 연산자를 사용하여 현재 페이지가 첫 페이지면 '#' (링크가 비활성화됨)를, 

그렇지 않으면 다음과 같은 페이지로 이동하는 링크를 생성한다. 

 

 

<span th:each="page: ${#numbers.sequence(startPage, endPage)}">

타임리프의 반복문 사용하여 startPage부터 endPage까지의 숫자 시퀀스를 생성한다.

이는 페이지 블록에 표시할 번호들을 나타낸다.

 

<span th:if="${page == boardList.number + 1}" th:text="${page}"></span>

현재 페이지 번호에 해당하는 경우, 링크 없이 숫자만을 출력한다. 

타임리프의 조건문을 사용하여 현재 페이지에 해당하는 경우에만 숫자를 표시한다.

 

<span th:unless="${page == boardList.number + 1}">

현재 페이지 번호가 아닌 경우, 링크를 생성한다.

th:unless는 주어진 조건이 거짓인 경우에만 내용을 처리한다. 

따라서 현재 페이지 번호와 다른 페이지 번호에 대해 링크를 생성하게 된다.

 

<a th:href="@{/board/paging(page=${page})}" th:text="${page}"></a>

링크의 URL은 /board/paging 이고, 페이지 번호는 page 변수로 동적으로 지정된다.

또한, 링크의 텍스트로는 해당 페이지 번호가 표시된다.

 

 

 

 

 

 

 

 

 

 

 

 

실행 ㄱㄱㄱㄱㄱ

 

http://localhost:8092/board/paging?page=1

 

 

http://localhost:8092/board/paging?page=4