스프링 부트, JPA, Thymeleaf를 이용한 페이징 처리 4 - 페이징 구현 (화면)

이제 Controller에서 데이터를 model에 담아 view로 넘겼기 때문에 마지막으로 이전에 생성한 view에서 paging 로직을 개발하면된다.

  • 게시물 리스트 화면에 뿌리기

먼저 게시물 리스트를 화면에 보여주는 것을 먼저 구현해보자.
간단하게 Controller에서 보낸 Page를 th:each를 사용해서 하나씩 뽑아 쓰기만 하면된다.

  • board.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>게시판</title>
<link rel="stylesheet" href="/webjars/bootstrap/3.3.5/css/bootstrap.min.css">
</head>
<body class="container">

<div class="jumbotron">
<h2>스프링 부트 게시판</h2>
</div>

<table class="table">
<tr>
<th>글 번호</th>
<th>글쓴이</th>
<th>글 제목</th>
</tr>
<tr th:each="board: ${boardList}">
<td th:text="${board.id}"></td>
<td th:text="${board.writer.name}"></td>
<td th:text="${board.title}"></td>
</tr>
</table>

<nav style="text-align: center;">
<ul class="pagination">
<li class="disabled">
<a href="#" aria-label="First">
<span aria-hidden="true">First</span>
</a>
</li>
<li class="disabled">
<a href="#" aria-label="Previous">
<span aria-hidden="true">&lt;</span>
</a>
</li>
<li class="active"><a href="#">1</a></li>
<li><a href="#">2</a></li>
<li><a href="#">3</a></li>
<li><a href="#">4</a></li>
<li><a href="#">5</a></li>
<li>
<a href="#" aria-label="Next">
<span aria-hidden="true">&gt;</span>
</a>
</li>
<li>
<a href="#" aria-label="Last">
<span aria-hidden="true">Last</span>
</a>
</li>
</ul>
</nav>

</body>
</html>

위는 전체 html 코드이고 아래는 변경된 부분이다.

1
2
3
4
5
<tr th:each="board: ${boardList}">
<td th:text="${board.id}"></td>
<td th:text="${board.writer.name}"></td>
<td th:text="${board.title}"></td>
</tr>

JSTL의 c:forEach와 비슷하게 위와 같이 th:each와 th:text를 사용하면 된다.

  • 페이지 버튼 구현

위 코드에서 pagination 관련 코드를 변경한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<nav style="text-align: center;">
<ul class="pagination"
th:with="start=${T(Math).floor(boardList.number/10)*10 + 1},
last=(${start + 9 < boardList.totalPages ? start + 9 : boardList.totalPages})">
<li>
<a th:href="@{/boards(page=1)}" aria-label="First">
<span aria-hidden="true">First</span>
</a>
</li>

<li th:class="${boardList.first} ? 'disabled'">
<a th:href="${boardList.first} ? '#' :@{/boards(page=${boardList.number})}" aria-label="Previous">
<span aria-hidden="true">&lt;</span>
</a>
</li>

<li th:each="page: ${#numbers.sequence(start, last)}" th:class="${page == boardList.number + 1} ? 'active'">
<a th:text="${page}" th:href="@{/boards(page=${page})}"></a>
</li>

<li th:class="${boardList.last} ? 'disabled'">
<a th:href="${boardList.last} ? '#' : @{/boards(page=${boardList.number + 2})}" aria-label="Next">
<span aria-hidden="true">&gt;</span>
</a>
</li>

<li>
<a th:href="@{/boards(page=${boardList.totalPages})}" aria-label="Last">
<span aria-hidden="true">Last</span>
</a>
</li>
</ul>
</nav>

코드가 은근히 복잡하지만 하나씩 뜯어보면 간단하다.

  • 변수 설정
1
2
3
<ul class="pagination"
th:with="start=${T(Math).floor(boardList.number/10)*10 + 1},
last=(${start + 9 < boardList.totalPages ? start + 9 : boardList.totalPages})">

그나마 제일 복잡한 로직이 여기다.
th:with는 해당 태그를 scope로 갖는 변수를 선언할 수 있게 해주는 속성이다.
start=${T(Math).floor(boardList.number/10)*10 + 1} : 현제 페이지를 통해 현재 페이지 그룹의 시작 페이지를 구하는 로직이다.
last=(${start + 9 < boardList.totalPages ? start + 9 : boardList.totalPages}) : 전체 페이지 수와 현재 페이지 그룹의 시작 페이지를 통해 현재 페이지 그룹의 마지막 페이지를 구하는 로직이다.

  • 첫 페이지로 가기 버튼
1
2
3
4
5
<li>
<a th:href="@{/boards(page=1)}" aria-label="First">
<span aria-hidden="true">First</span>
</a>
</li>

href 속성을 위와 같이 th:href로 줄 수 있다.
href를 사용하지 않고 굳이 th:href를 사용한 이유는 c:url과 같이 컨텍스트명이 추가되더라도 리소스 path에 컨텍스트명을 붙여 리소스를 가져오기 때문에
컨텍스트명 유무에 상관없이 항상 정상적으로 리소스를 가져올 수 있게 해주는 속성이다.
그리고 파라미터는 (key=value) 형식으로 표기한다.

  • 이전 페이지로 가기 버튼
1
2
3
4
5
<li th:class="${boardList.first} ? 'disabled'">
<a th:href="${boardList.first} ? '#' :@{/boards(page=${boardList.number})}" aria-label="Previous">
<span aria-hidden="true">&lt;</span>
</a>
</li>

th:class는 위에 보이는 것처럼 조건을 통해 class를 지정할 수 있다.
위에서는 현재 페이지가 첫번째 페이지면 disabled를 걸어놓는 코드이다.
th:href 역시 조건을 통해 href를 지정할 수 있는데 위에서는 삼항연산자를 사용하였다.
첫 페이지라면 href에 #을 지정하고 아니라면 현재 페이지의 page number를 지정한다.
(현재 페이지의 page number를 지정하는 이유는 page number는 index 처럼 0에서 시작하기 때문에
현재 페이지 - 1이 자동으로 적용된다.)

  • 현재 페이지 그룹의 페이지 나열
1
2
3
<li th:each="page: ${#numbers.sequence(start, last)}" th:class="${page == boardList.number + 1} ? 'active'">
<a th:text="${page}" th:href="@{/boards(page=${page})}"></a>
</li>

th:ech를 사용해서 현재 페이지 그룹의 페이지를 나열한다.
이때 th:class 속성에서 현재페이지일 경우 ‘active’ class를 추가하는 로직이 있다.

  • 다음 페이지로 가기 버튼
1
2
3
4
5
<li th:class="${boardList.last} ? 'disabled'">
<a th:href="${boardList.last} ? '#' : @{/boards(page=${boardList.number + 2})}" aria-label="Next">
<span aria-hidden="true">&gt;</span>
</a>
</li>

이전 페이지로 가기 버튼을 만들때와 동일하기 때문에 생략

  • 마지막 페이지로 가기 버튼
1
2
3
4
5
<li>
<a th:href="@{/boards(page=${boardList.totalPages})}" aria-label="Last">
<span aria-hidden="true">Last</span>
</a>
</li>

첫 페이지로 가기 버튼을 만들때와 동일하기 때문에 생략


일반적으로 게시판은 최근 등록한 게시물 순으로 노출되는데 지금은 1번 게시물이 1페이지에 나온다.

sorting이 필요하다!

Pageable에는 sort가 프로퍼티가 있다.

이 sort를 사용하여 최신순으로 게시물을 가져올 것이다.

  • BoardService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.devson.pagination.web.service;

import com.devson.pagination.web.domain.BoardEntity;
import com.devson.pagination.web.repository.BoardRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

@Service
public class BoardService {
private BoardRepository boardRepository;

public BoardService(BoardRepository boardRepository) {
this.boardRepository = boardRepository;
}

public Page<BoardEntity> getBoardList(Pageable pageable) {
int page = (pageable.getPageNumber() == 0) ? 0 : (pageable.getPageNumber() - 1);
pageable = PageRequest.of(page, 10, new Sort(Sort.Direction.DESC, "id")); // <- Sort 추가

return boardRepository.findAll(pageable);
}
}

위와 같이 PageRequest.of 메서드를 통해 Pageable 객체를 생성할 때 Sort를 사용한 코드를 추가하면
ID 순 즉, 생성 순으로 BOARD 테이블에서 데이터를 객체를 가져온다.

소스를 적용한 후 게시판에 접속해보자

위와 같이 최신 등록 순으로 나오는 것을 확인할 수 있고
로그를 확인하면 아래와 같이 order by가 추가된 쿼리가 나오는 것을 확인할 수 있다.

1
2
3
4
5
6
7
8
9
Hibernate: 
select
boardentit0_.id as id1_0_,
boardentit0_.title as title2_0_,
boardentit0_.user_id as user_id3_0_
from
board boardentit0_
order by
boardentit0_.id desc limit ?
Share