Spring boot 게시판 페이징처리
1. nav
<div class="menu">
<!-- /board?page=1
7페이지에 있는 33번 글을 조회
rest api > /board/7/33
/board/33 => 33번글 /board/33?page=7
-->
<a th:href="@{/board(page=1)}">페이징 목록</a>
</div>
http://localhost:8084/board?page=1
링크를 누르면 아래 주소로 이동이 된다.
2. Controller
Pageable에 java를 import하는것이 아닌 위에 체크되있는 부분을 import한다.
@GetMapping
public String paging(@PageableDefault(page = 1) Pageable pageable, Model model) {
System.out.println("page = "+ pageable.getPageNumber());
return null;
}
페이지 번호가 잘 넘어오는지 확인하려고 sout를 찍어보았다.
만약 페이지 요청이 오지않고 이상한 값이 오게된다면 PageableDefault으로 page = 1을 Defalut로 설정했기 때문에 1페이지로 이동하게 된다.
int blockLimit = 3;
페이지 번호를 몇개까지 보여줄 것인지 설정한다.
int startPage = (((int) (Math.ceil((double) pageable.getPageNumber() / blockLimit))) - 1) * blockLimit + 1;
시작 페이지를 계산한다.
int endPage = ((startPage + blockLimit - 1) < boardDTOPage.getTotalPages()) ? startPage + blockLimit - 1 : boardDTOPage.getTotalPages();
보여줄 페이지의 마지막 번호를 계산한다.
삼항 연산자를 사용하였다.
Spring Framework때 MaxPage(페이지의 마지막 페이지)를 구했었는데 Spring boot에서는 endPage 하나로 처리한다.
if((startPage+blockLimit-1)<boardDTOPage.getTotalPages()) {
endPage = startPage+blockLimit-1;
}else {
endPage = boardDTOPage.getTotalPages();
}
위의 삼항연산자를 if문으로 쓴다면 이렇게 표현한다.
// /board?page=1
@GetMapping
public String paging(@PageableDefault(page = 1) Pageable pageable, Model model) {
System.out.println("page = "+ pageable.getPageNumber());
Page<BoardDTO> boardDTOPage = boardService.paging(pageable);
model.addAttribute("boardList",boardDTOPage);
// 시작페이지(startPage), 마지막페이지(endPage) 값 계산
// 하단에 보여줄 페이지 갯수 3개
int blockLimit = 3;
int startPage = (((int) (Math.ceil((double) pageable.getPageNumber() / blockLimit))) - 1) * blockLimit + 1;
int endPage = ((startPage + blockLimit - 1) < boardDTOPage.getTotalPages()) ? startPage + blockLimit - 1 : boardDTOPage.getTotalPages();
model.addAttribute("startPage",startPage);
model.addAttribute("endPage",endPage);
return "boardPages/boardPaging";
}
model로 값을 담아서 HTML로 전송한다.
3. Service
Spring boot의 JPA에서는 페이지 번호가 0부터 시작하기 때문에 페이지 번호를 1번을 요청하면 2페이지 부터 보여질 것이다.
int page = pageable.getPageNumber() - 1;
Service에서 번호에 -1을 해줘서 번호를 맞춰준다.
int pageLimit = 5;
페이지를 5개까지 보여준다.
boardRepository.findAll(PageRequest.of(page,pageLimit,Sort.by(Sort.Direction.DESC,"id")));
public Page<BoardDTO> paging(Pageable pageable) {
int page = pageable.getPageNumber() - 1;
int pageLimit = 5;
Page<BoardEntity> boardEntities =
boardRepository.findAll(PageRequest.of(page, pageLimit, Sort.by(Sort.Direction.DESC, "id")));
Page<BoardDTO> boardDTOS = boardEntities.map(boardEntity -> BoardDTO.builder()
.id(boardEntity.getId())
.boardTitle(boardEntity.getBoardTitle())
.boardWriter(boardEntity.getBoardWriter())
.createdAt(UtilClass.dateFormat(boardEntity.getCreatedAt()))
.boardHits(boardEntity.getBoardHits())
.build());
return boardDTOS;
}
4. BoardPaging.HTML
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:replace="component/config :: config"></th:block>
</head>
<body>
<div th:replace="component/header :: header"></div>
<div th:replace="component/nav :: nav"></div>
<div id="section">
<div class="container" id="paging-list">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">id</th>
<th scope="col">writer</th>
<th scope="col">title</th>
<th scope="col">date</th>
<th scope="col">hits</th>
</tr>
</thead>
<tbody>
<tr th:each="board: ${boardList}">
<td th:text="${board.id}"></td>
<td th:text="${board.boardWriter}"></td>
<td><a th:href="@{|/board/${board.id}|}" th:text="${board.boardTitle}">제목</a></td>
<td th:text="${board.createdAt}"></td>
<td th:text="${board.boardHits}"></td>
</tr>
</tbody>
</table>
<div class="container">
<ul class="pagination justify-content-center">
<li class="page-item">
<!--첫페이지 요청 링크 /board?page=1 -->
<a class="page-link" th:href="@{/board(page=1)}">
<span>First</span>
</a>
</li>
<li th:class="${boardList.first} ? 'disabled'" class="page-item">
<!--boardList.first: isFirst()
boardList.number: getNumber()-->
<a class="page-link" th:href="${boardList.first} ? '#' : @{/board(page=${boardList.number})}">
<!-- 사용자 3페이지, number 2 /board?page=2 -->
<span><</span> <!-- < -->
</a>
</li>
<!-- startPage ~ endPage 까지 숫자를 만들어주는 역할
for(int page=startPage; page<=endPage; page++) -->
<li th:each="page: ${#numbers.sequence(startPage, endPage)}"
th:class="${page == boardList.number + 1} ? 'page-item active'" class="page-item">
<a class="page-link" th:text="${page}" th:href="@{/board(page=${page})}"></a>
</li>
<!-- 다음 페이지 요청
현재 3페이지를 보고 있다면 다음 페이지는 4페이지임.
getNumber() 값은 2임.
따라서 4페이지를 보고 싶다면 getNumber()+2를 해야 컨트롤러에 4를 요청할 수 있음. -->
<li th:class="${boardList.last} ? 'disabled'">
<a class="page-link" th:href="${boardList.last} ? '#' : @{/board(page=${boardList.number + 2})}">
<!-- 사용자 3페이지, number 2 /board?page=4 -->
<span>></span>
</a>
</li>
<li class="page-item">
<a class="page-link" th:href="@{/board(page=${boardList.totalPages})}">
<span>Last</span>
</a>
</li>
</ul>
</div>
</div>
<div th:replace="component/footer :: footer"></div>
</body>
</html>
<!--첫페이지 요청 링크 /board?page=1 -->
<a class="page-link" th:href="@{/board(page=1)}">
<span><i class="bi bi-chevron-double-left"></i></span>
</a>
</li>
첫 페이지는 1번이므로 맨 처음으로 가는 버튼을 설정한다.
<li th:class="${boardList.first} ? 'disabled'" class="page-item">
<!--boardList.first: isFirst()
th:class 조건에 따라서 'disabled' 속성을 적용한다.
boardList.number: getNumber()-->
<a class="page-link" th:href="${boardList.first} ? '#' : @{/board(page=${boardList.number})}">
<!-- 사용자 3페이지, number 2 /board?page=2 -->
<span><i class="bi bi-chevron-left"></i></span> <!-- < -->
</a>
</li>
th:class 타임리프 문법을 사용하여 해당 조건이 만족하면 disabled 속성을 적용 , 아니라면 page-item 속성을 적용한다.
페이지의 StartPage를 설정하는 문구다.
<!-- startPage ~ endPage 까지 숫자를 만들어주는 역할
for(int page=startPage; page<=endPage; page++) -->
<li th:each="page: ${#numbers.sequence(startPage, endPage)}"
th:class="${page == boardList.number + 1} ? 'page-item active'" class="page-item">
<a class="page-link" th:text="${page}" th:href="@{/board(page=${page})}"></a>
</li>
startPage 에서 endPage까지 숫자를 만들어주려고
th:each문법으로 , #numbers.sequence(startPage,endPage)를 사용했다.
<!-- 다음 페이지 요청
현재 3페이지를 보고 있다면 다음 페이지는 4페이지임.
getNumber() 값은 2임.
따라서 4페이지를 보고 싶다면 getNumber()+2를 해야 컨트롤러에 4를 요청할 수 있음. -->
<li th:class="${boardList.last} ? 'disabled'">
<a class="page-link" th:href="${boardList.last} ? '#' : @{/board(page=${boardList.number + 2})}">
<!-- boardList.last라면 # , 아니라면 @{/board(page=${boardList.number + 2})
사용자 3페이지, number 2 /board?page=4 -->
<span><i class="bi bi-chevron-right"></i></span>
</a>
</li>
다음 페이지 버튼을 설정하는데 3페이지를 보고 있다면 다음 페이지는 4페이지 지만 getNumber()의 값은 2이기에
+2를 해주어야 컨트롤러에서 맞는 페이지를 찾아서 리턴할 수 있다.
만약 보여주는 페이지 번호의 마지막 ( last ) 라면 # 를 주고 , 아니라면 다음 페이지로 가는 버튼을 설정한다.
<li class="page-item">
<a class="page-link" th:href="@{/board(page=${boardList.totalPages})}">
<span><i class="bi bi-chevron-double-right"></i></span>
</a>
</li>
boardList.totalPages로 마지막 번호로 보내는 버튼도 생성한다.
적용이 잘 되었다.
5. 특정 페이지에서 상세조회후 다시 특정 페이지로
<td><a th:href="@{|/board/${board.id}?page=${boardList.number+1}|}" th:text="${board.boardTitle}">제목</a>
제목을 눌러서 상세조회하는 a 태그에 ?page=${boardList.number+1}을 추가한다.
@GetMapping("/{id}")
@Transactional
public String findById(@PathVariable Long id,@RequestParam("page") int page, Model model) {
boardService.updateHits(id);
BoardDTO boardDTO = null;
model.addAttribute("page",page);
try {
boardDTO = boardService.findById(id);
model.addAttribute("board", boardDTO);
List<CommentDTO> commentDTOList = commentService.findById(id);
if(commentDTOList.size()>0) {
model.addAttribute("comment", commentDTOList);
}else {
model.addAttribute("comment",null);
}
return "boardPages/boardDetail";
} catch (NoSuchElementException e) {
return "boardPages/boardNotFound";
}
}
컨트롤러 매개변수에 RequestParam으로 page를 받고 , model로 값을 전송
<button class="btn btn-primary" onclick="list_req()">목록</button>
목록가는 버튼에
const list_req = () => {
location.href = "/board/?page="+[[${page}]];
// const page = [[${page}]];
// location.href = "/board/?page="+page;
}