티스토리 뷰
01) NoticeController
@GetMapping(value = "/list.do")
public String paging() {
return "noticeList";
}
일단 resources/templates/noticeList.html을 불러옴
02) Notice.html
<!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="i: ${#numbers.sequence(1, 10)}">
<td th:text="${i}"></td>
<td th:text="글쓴이 + ${i}"></td>
<td th:text="제목 + ${i}"></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"><</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">></span>
</a>
</li>
<li>
<a href="#" aria-label="Last">
<span aria-hidden="true">Last</span>
</a>
</li>
</ul>
</nav>
</body>
</html>
th 속성은 Thymeleaf에서 제공하는 기능으로 JSP로 치면 <c:forEach>와 같음.
아래 코드를 통해 table에 일일이 tr+td태그를 넣지 않아도 화면에 띄울 수 있음.
<tr th:each="i: ${#numbers.sequence(1, 10)}">
<td th:text="${i}"></td>
<td th:text="글쓴이 + ${i}"></td>
<td th:text="제목 + ${i}"></td>
</tr>
03) NoticeController 수정
Pageable은 Spring에서 페이징 기능을 위한 파라미터들을 추상화 시킨 인터페이스임. 이 Pageable을 아래와 같이 Controller의 RequestMapping 메서드 인자로 넣을 수 있음.
@GetMapping(value = "/list.do")
public String paging(@PageableDefault Pageable pageRequest) {
return "noticeList";
}
Pageable을 디버그 모드로 확인하면 pageable 객체에 page, size, sort 프로퍼티가 있는 것을 확인 가능.
04) Repository 생성
- NoticeRepository 생성함.
@Repository
public interface NoticeRepository extends JpaRepository<Notice, Long> {
}
05) Service 생성
- NoticeService
@Service
public class NoticeService {
private NoticeRepository noticeRepository;
public NoticeService(NoticeRepository noticeRepository) {
this.noticeRepository = noticeRepository;
}
public Page<Notice> getNoticeList(Pageable pageable) {
int page = (pageable.getPageNumber() == 0) ? 0 : (pageable.getPageNumber() - 1); // page는 index 처럼 0부터 시작
pageable = PageRequest.of(page, 10, Sort.Direction.ASC, "seq"); // Sort 추가
return noticeRepository.findAll(pageable);
}
noticeRepository.findAll 메서드의 파라미터로 Pageable 객체를 사용할 수 있음.
Page 객체를 리턴하면 Pageable과 마찬가지로 페이징 기능을 위해 추상화 시킨 인터페이스임.
int page = (pageable.getPageNumber() == 0) ? 0 : (pageable.getPageNumber() - 1);
위 부분에서는 Pageable의 page는 index 처럼 0부터 시작임.
하지만 주로 게시판에서는 1부터 시작하기 때문에 사용자가 보려는 페이지에서 -1처리를 해준 것임.
pageable = PageRequest.of(page, 10);
Pageable 인터페이스를 확인해보면 알겠지만 getter는 있지만 setter는 없음.
그래서 PageRequest.of 메서드를 사용하여 새로운 pageable 객체를 생성함.
return noticeRepository.findAll(pageable);
사용하는 SQL의 문법에 맞게 페이징 쿼리를 만들어서 DB에 데이터를 가져옴.
06) noticeList.html 수정
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="board/layout/basic">
<head>
<meta charset="UTF-8">
<title>일더하기 Work+</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
<script src="/webjars/bootstrap/3.3.4/dist/js/bootstrap.min.js"></script>
</head>
<body>
<h1>공지사항</h1>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">번호</th>
<th scope="col">제목</th>
<th scope="col">조회수</th>
<th scope="col">작성자</th>
<th scope="col">작성일자</th>
</tr>
</thead>
<tbody>
<tr th:each="notice:${noticeDtoList}">
<td>
<span th:text="${notice.seq}"></span>
</td>
<td>
<a th:href="|@{/workplus/notice/view.do}?noticeSeq=${notice.seq}|" th:text="${notice.title}"></a>
</td>
<td>
<span th:text="${notice.viewCnt}"></span>
</td>
<td>
<span th:text="${notice.writer}"></span>
</td>
<td>
<span th:text="${notice.createdDate}"></span>
</td>
</tr>
</tbody>
</table>
<nav>
<div th:with="start=${T(Math).floor(noticeDtoList.number/10)*10 + 1},
last=(${start + 9 < noticeDtoList.totalPages ? start + 9 : noticeDtoList.totalPages})">
<a th:href="@{list1.do(page=1)}" aria-label="First">
<span aria-hidden="true">First</span>
</a>
<a th:class="${noticeDtoList.first} ? 'disabled'" th:href="${noticeDtoList.first} ? '#' :@{list1.do(page=${noticeDtoList.number})}" aria-label="Previous">
<span aria-hidden="true"><</span>
</a>
<a th:each="page: ${#numbers.sequence(start, last)}" th:class="${page == noticeDtoList.number + 1} ? 'active'" th:text="${page}" th:href="@{list1.do(page=${page})}"></a>
<a th:class="${noticeDtoList.last} ? 'disabled'" th:href="${noticeDtoList.last} ? '#' : @{list1.do(page=${noticeDtoList.number + 2})}" aria-label="Next">
<span aria-hidden="true">></span>
</a>
<a th:href="@{list1.do(page=${noticeDtoList.totalPages})}" aria-label="Last">
<span aria-hidden="true">Last</span>
</a>
</div>
</nav>
<div>
<button onclick="location.href='/workplus/notice/write.do'">글작성</button>
</div>
</body>
</html>
변수 설정
<ul class="pagination"
th:with="start=${T(Math).floor(noticeList.number/10)*10 + 1},
last=(${start + 9 < noticeList.totalPages ? start + 9 : noticeList.totalPages})">
th:with는 해당 태그를 scope로 갖는 변수를 선언할 수 있게 해주는 속성임.
start=${T(Math).floor(noticeList.number/10)*10 + 1} : 현재 페이지를 통해 현재 페이지 그룹의 시작 페이지를 구하는 로직이다.
last=(${start + 9 < noticeList.totalPages ? start + 9 : noticeList.totalPages}) : 전체 페이지 수와 현재 페이지 그룹의 시작 페이지를 통해 현재 페이지 그룹의 마지막 페이지를 구하는 로직이다.
<a th:href="@{list1.do(page=1)}" aria-label="First">
<span aria-hidden="true">First</span>
</a>
위는 첫 페이지로 가는 버튼이며, href 속성을 위와 같이 th:href로 줄 수 있다.
href를 사용하지 않고 th:href를 사용한 이유는 c:url과 같이 컨텍스트명이 추가되더라도 리소스 path에 컨텍스트명을 붙여 리소스를 가져오기 때문에 컨텍스트명 유무에 상관없이 항상 정상적으로 리소스를 가져올 수 있게 해주는 속성이다.
그리고 파라미터는 (key=value) 형식으로 표기한다.
<a th:class="${noticeDtoList.first} ? 'disabled'" th:href="${noticeDtoList.first} ? '#' :@{list1.do(page=${noticeDtoList.number})}" aria-label="Previous">
<span aria-hidden="true"><</span>
</a>
위는 이전 페이지로 가기 버튼이며 th:class는 위에 보이는 것처럼 조건을 통해 class를 지정할 수 있다.
위에서는 현재 페이지가 첫번째 페이지면 disabled를 걸어놓는 코드이다.
th:href 역시 조건을 통해 href를 지정할 수 있는데 위에서는 삼항연산자를 사용함.
첫 페이지라면 href에 #을 지정하고 아니라면 현재 페이지의 page number를 지정함.
현재 페이지의 page number를 지정하는 이유는 page number는 index 처럼 0에서 시작하기 때문에 현재 페이지 -1이 자동 적용됨.
<a th:each="page: ${#numbers.sequence(start, last)}" th:class="${page == noticeDtoList.number + 1} ? 'active'" th:text="${page}" th:href="@{list1.do(page=${page})}"></a>
위는 현재 페이지 그룹의 페이지 나열이며 th:each를 사용해서 현재 페이지 그룹의 페이지를 나열한다.
이때 th:class 속성에서 현재페이지일 경우 'active' class를 추가하는 로직이 있다.
<a th:class="${noticeDtoList.last} ? 'disabled'" th:href="${noticeDtoList.last} ? '#' : @{list1.do(page=${noticeDtoList.number + 2})}" aria-label="Next">
<span aria-hidden="true">></span>
</a>
위는 다음 페이지로 가기 버튼으로 이전 페이지로 가기 버튼을 만들때와 비슷하다.
<a th:href="@{list1.do(page=${noticeDtoList.totalPages})}" aria-label="Last">
<span aria-hidden="true">Last</span>
</a>
위는 마지막 페이지로 가기 버튼이다.
'Spring boot > JPA' 카테고리의 다른 글
07. JPA - QueryDsl 환경설정 (0) | 2022.01.27 |
---|---|
06. JPA - @Temporal annotation (0) | 2021.11.16 |
04. JPA - Pageable (페이징 처리) (0) | 2021.06.14 |
03. JPA - Update 테스트 시 유의 사항 ( @Transactional, @Rollback(false) ) (0) | 2021.06.09 |
02. JPA - orElseThrow() 관련 (0) | 2021.05.31 |