페이징
- 게시물 목록이 화면에 뜰 때 아래에 1, 2, 3 .. 등의 넘기는 숫자와 한꺼번에 넘길 수 있거나 되돌릴 수 있는 next, prev 같은 기능을 추가해보려고한다.
BoardMapper.xml
<select id="findAll" resultType="board">
SELECT * FROM tbl_board
ORDER BY board_no DESC
LIMIT #{pageStart}, #{amount}
</select>
- LIMIT을 걸어서 페이징의 범위를 설정
- pageStart는 그 페이지의 첫번 째 게시물번호, amount는 한 페이지당 게시물 목록 수를 나타낸다.
Page
package com.study.springstudy.springmvc.chap04.common;
import lombok.*;
@Getter
@ToString
@EqualsAndHashCode
@AllArgsConstructor
public class Page {
private int pageNo;
private int amount;
public Page() {
this.pageNo = 1;
this.amount = 6;
}
public void setPageNo(int pageNo) {
if (pageNo < 1 || pageNo > Integer.MAX_VALUE) {
this.pageNo = 1;
return;
}
this.pageNo = pageNo;
}
public void setAmount(int amount) {
if (amount < 6 || amount > 60) {
this.amount = 6;
return;
}
this.amount = amount;
}
public int getPageStart() {
return (this.pageNo - 1) * this.amount;
}
}
- Page 클래스를 보면 PageNO, amount 필드가 보인다. 하지만 나는 sql에 pageStart를 넣었는데 어떻게 된일일까?
-> getter를 만들면 sql에서는 필드를 생성한 줄 알기 때문에 필드로 사용가능
pageNo을 sql에 보내면되는거 아닌가 왜 굳이 pageStart를 사용해야할까?
- 주석을 보면
M페이지 -> LIMIT (M - 1) * N, N
인 것을 알 수 있다.
여기서 M = pageNo
, N = amount
이지만 (M - 1) * N
을 sql에 보내줘야 하는 것을 볼 수 있다. 즉, pageStart = (M - 1) * N
을 나타내고 이것을 sql에 보내주어야한다.
PageMaker
- 페이지 화면 렌더링에 필요한 정보들을 계산하는 PageMaker 클래스를 만들어준다.
@Getter @ToString
@EqualsAndHashCode
public class PageMaker {
private static final int PAGE_COUNT = 10;
private int begin, end, finalPage;
private boolean prev, next;
private int totalCount;
private Page pageInfo;
public PageMaker(Page page, int totalCount) {
this.pageInfo = page;
this.totalCount = totalCount;
makePageInfo();
}
private void makePageInfo() {
this.end = (int) (Math.ceil((double) pageInfo.getPageNo() / PAGE_COUNT) * PAGE_COUNT);
this.begin = this.end - PAGE_COUNT + 1;
this.finalPage = (int) Math.ceil((double) totalCount / pageInfo.getAmount());
if (finalPage < this.end) {
this.end = finalPage;
}
this.prev = begin != 1;
this.next = this.end < finalPage;
}
}
- PageMaker 객체를 생성하면 page정보, 총 게시물 수, 페이지 생성 알고리즘을 모두 가져올 수 있다.
-> totalCount는 DB에서 가져온다.
- Controller에서 Service로 요청
- Service에서 mapper로 반환
- Mapper에서 sql 구현
- end값 공식 적용
- begin값은 end값 - 한 화면에 보여줄 페이지 수 + 1
Search
- 검색했을 때에도 페이징이 적용되어야하므로 Page를 상속한다.
package com.study.springstudy.springmvc.chap04.common;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter @Setter @ToString
@EqualsAndHashCode
public class Search extends Page {
private String keyword, type;
public Search() {
this.keyword = "";
}
}
page 파라미터 추가
- BoareMapper(interface), BoardService, BoardController의 목록을 조회하는 부분에 파라미터로 page를 추가해주어 페이징을 적용시킨다.
Controller
Search page
로 파라미터를 주어 Search클래스에 있는 필드들까지 총 4개의 필드를 사용할 수 있게했다.
Service
BoardMapper.xml 수정 (Search클래스 추가, 검색에 대한 조건을 다뤄서 sql나눔)
- tc는 title or content를 나타냄
- LIKE 뒤에
'%내용%'
형태로 써야하지만 %% 사이에는 #{}가 올 수 없기 때문에 CONCAT으로 씀
<select id="findAll" resultType="board">
SELECT * FROM tbl_board
<if test="type == 'title'">
WHERE title LIKE CONCAT('%', #{keyword}, '%')
</if>
<if test="type == 'content'">
WHERE content LIKE CONCAT('%', #{keyword}, '%')
</if>
<if test="type == 'writer'">
WHERE writer LIKE CONCAT('%', #{keyword}, '%')
</if>
<if test="type == 'tc'">
WHERE title LIKE CONCAT('%', #{keyword}, '%')
OR content LIKE CONCAT('%', #{keyword}, '%')
</if>
ORDER BY board_no DESC
LIMIT #{pageStart}, #{amount}
</select>
list.jsp
<div class="bottom-section">
<nav aria-label="Page navigation example">
<ul class="pagination pagination-lg pagination-custom">
<c:if test="${maker.pageInfo.pageNo != 1}">
<li class="page-item">
<a class="page-link" href="/board/list?pageNo=1"><<</a>
</li>
</c:if>
<c:if test="${maker.prev}">
<li class="page-item">
<a class="page-link" href="/board/list?pageNo=${maker.begin -1}"
>prev</a>
</li>
</c:if>
<c:forEach var="i" begin="${maker.begin}" end="${maker.end}">
<li data-page-num="${i}" class="page-item">
<a class="page-link" href="/board/list?pageNo=${i}">${i}</a>
</li>
</c:forEach>
<c:if test="${maker.next}">
<li class="page-item">
<a class="page-link" href="/board/list?pageNo=${maker.end + 1}"
>next</a
>
</li>
</c:if>
<c:if test="${maker.pageInfo.pageNo != maker.finalPage}">
<li class="page-item">
<a class="page-link" href="/board/list?pageNo=${maker.finalPage}">>></a>
</li>
</c:if>
</ul>
</nav>
</div>
- 클릭한 페이지만 다른색으로 활성화되는 함수도 추가
function appendActivePage() {
const currentPage = "${maker.pageInfo.pageNo}";
const $li = document.querySelector(
`.pagination li[data-page-num="\${currentPage}"]`
);
$li.classList.add("active");
}
appendActivePage();
.pagination li[data-page-num="\${currentPage}"]
의 경우
-> 예를 들어 현재 페이지가 3인 경우 [data-page-num="3"] 이다.
<div class="top-section">
<!-- 검색창 영역 -->
<div class="search">
<form action="/board/list" method="get">
<select class="form-select" name="type" id="search-type">
<option value="title" selected>제목</option>
<option value="content">내용</option>
<option value="writer">작성자</option>
<option value="tc">제목+내용</option>
</select>
<input type="text" class="form-control" name="keyword">
<button class="btn btn-primary" type="submit">
<i class="fas fa-search"></i>
</button>
</form>
</div>
</div>
- form 태그안에 있는 sql문의 name은 필드명과 일치해야함
-> Search 클래스의 type, keyword와 일치!