URL으니 파라미터를 이용해서 정상적으로 원하는 페이지로 이동하는 것을 확인했다면
화면 밑에 페이지 번호를 표시하고 사용자가 페이지 번호를 클릭할 수 있게 처리한다.
페이지를 보여주는 작업은 다음과 같은 과정을 통해서 진행한다.
- 브라우저 주소창에서 페이지 번호를 전달해서 결과를 확인하는 단꼐
- JSP에서 페이지 번호를 출력하는 단계
- 각 페이지 번호에 클릭 이벤트 처리
- 전체 데이터 개수를 반영해서 페이지 번호 조절
패이지 처리는 단순히 링크의 연결이기 때문에 어렵지는 않지만
다음 그림과 같이 목록 페이지에서 조회 페이지,수정 삭제 페이지까지 페이지 번호가 계속해서 유지되어야만 하기 때문에 끝까지 싱경써야 하는 부분들이 많은 편이다.다음 그림은 페이지 번호에 어떤 작업을 하던 유지되면서 링크가 연결되는 모습이다.

페이징 처리할 때 필요한 정보들
화면에 체이징 처리를 하기 위해서는 우선적으로 여러 가지 필요한 정보들이 존재한다.
화면에 페이지는 크게 다음과 같은 정보들이 필요하다.
- 현재 페이지 번호(page)
- 이전과 다음으로 이동 가능한 링크의 표시(prev,next)
- 화면에서 보여지는 페이지의 시작 번호와 끝 번호(startPage,endPage)
끝 페이지 번호와 시작 페이지 번호
페이징 처리를 하기 위해서 우선적으로 필요한 정보는 현재 사용자가 보고 있는 페이지의 정보이다.
예를 들어 사용자가 5페이지를 본다면 화면의 페이지 번호는 1부터 시작하지만,사용자가 19페이지를 본다면 11부터 시작해야 하기 때문이다.
흔히들 페이지를 계산할때 시작번호를 먼저 하려고 하지만 오히려 끝 번호를 먼저 계산해 두는 것이 수월하다.
끝 번호는 다음과 같은 공식으로 구할 수 있다.
this.endPage = (int)(Math.ceil(페이지번호/10.0))*10;
Math.ceil()은 소수점을 올림으로 처리하기 때문에 다음과 같은 상황이 가능하다.
- 1페이지의 경우:Math.ceil(0.1)*10 = 10
- 10페이지의 경우:Math.ceil(1)*10 = 10
- 11페이지의 경우:Math.ceil(1.1)*10 = 20
끝 번호는 아직 개선의 여지가 있다.만일 전체 데이터 수가 적다면 10페이지로 끝나면 안되는 상황이 생길 수도 있기 때문이다.그럼에도 끝 번호를 먼저 계산하는 이유는 시작 번호를 계산하기 수월하기 때문이다.
만일 화면에 10개씩 보여준다면 시작 번호는 무조건 끝 번호에서 9라는 값을 뺀 값이다.
this.startPage = this.endPage-9;
끝 번호는 전체 데이터 수에 의해서 영향을 받는다.예를 들어 10개씩 보여주는 경우 전체 데이터 수가 80개라고 가정하면 끝 번호는 10이 아닌 8이 되어야만 한다.
만일 끝 번호와 한 페이지당 출력되는 데이터 수의 곱이 전체 데이터 수보다 크다면 끝번호는 다시 total을 이용해서 다시 계산되어야 한다.
realEnd = (int)(Math.ceil((total*1.0)/amount));
if(realEnd <this.endPage){
this.endPage = realEnd;
}
먼저 전체 데이터 수(total)를 이용해서 진짜 끝 페이지가 몇 번까지 되는지를 계산한다.
만일 진짜 끝 페이지가 구해둔 끝 번호보다 작다면 끝번호는 작은 값이 되어야만 한다.
이전(prev)과 다음(next)
이전과 다음은 아주 간단히 구할 수 있다.
이전의 경우는 시작 번호가 1보다 큰 경우라면 존재하게 된다.
this.prev = this.startPage>1;
다음으로 가는 링크의 경우 위의 realEnd가 끝번호보다 큰 경우에만 존재하게 된다.
this.next = this.endPage<realEnd;
페이징 처리를 위한 클래스 설계
화면에 페이징 처리를 위해서 위와 같이 여러 정보가 필요하다면 클래스를 구성해서 처리하는 방식도 꽤 편한 방식이 될 수 있다.클래스를 구성하면 Controller 계층에서 JSP화면에 전달할 때에도 객체를 생성해서 Model에 담아 보내는 과정이 단순해지는 장점도 있다.
com.osk2090.domain 패키지에 PageDTO 클래스를 설계한다.
PageDTO.class
package com.osk2090.domain;
import lombok.Getter;
import lombok.ToString;
@Getter
@ToString
public class PageDTO {
private int startPage;
private int endPage;
private boolean prev, next;
private int total;
private Criteria cri;
public PageDTO(int total, Criteria cri) {
this.total = total;
this.cri = cri;
this.endPage = (int) (Math.ceil(cri.getPageNum() / 10.0)) * 10;
this.startPage = this.endPage - 9;
int realEnd = (int) (Math.ceil(total * 1.0) / cri.getAmount());
if (realEnd < this.endPage) {
this.endPage = realEnd;
}
this.prev = this.startPage > 1;
this.next = this.endPage < realEnd;
}
}
PageDTO는 생성자를 정의하고 Criteria와 전체 데이터 수를 파라미터로 지정한다.
Criteria안에는 페이지에서 보여주는 데이터 수와 현재 페이지 번호를 가지고 있기 때문에 이를 이용해서 필요한 모든 내용을 계산할 수 있다.
BoardController에서는 PageDTO를 사용할 수 있도록 Model에 담아서 화면에 전달해 줄 필요가 있다.
BoardController.class
@GetMapping("/list")
public void list(Criteria cri, Model model) {
log.info("list: " + cri);
model.addAttribute("list", service.getList(cri));
model.addAttribute("pageMaker", new PageDTO(cri, 123));
}
list()는 pageMaker라는 이름으로 PageDTO 클래스에서 객체를 만들어서 Model에 담아준다.
PageDTO를 구성하기 위해서는 전체 데이터 수가 필요한데 아직 그 처리가 이루어지지 않았으므로 임의의 값으로 123을 지정했다.
JSP에서 페이지 번호 출력
JSP에서 페이지 번호를 출력하는 부분은 JSTL을 이용해서 처리할 수 있다.
기존의 <table> 태그가 끝나는 직후에 페이지 처리를 추가한다.
list.jsp
</c:forEach>
</table>
<div class="pull-right">
<ul class="pagination">
<c:if test="${pageMaker.prev}">
<li class="paginate_button previous">
<a href="#">Previous</a>
</li>
</c:if>
<c:forEach var="num" begin="${pageMaker.startPage}"
end="${pageMaker.endPage}">
<li class="paginate_button">
<a href="#">${num}</a>
</li>
</c:forEach>
<c:if test="${pageMaker.next}">
<li class="paginate_button next">
<a href="#">Next</a>
</li>
</c:if>
</ul>
</div>
Model 창의 아래쪽에 별도의 <div class="row">를 구성하고 페이지 번호들을 출력한다.
pageMaker라는 이름으로 전달된 PageDTO를 이용해서 화면에 페이지 번호들을 출력한다.
예를들어 현재 total은 123이라는 숫자로 지정되어 있으므로 5페이지를 조회하면 next값은 true가 되어야 한다.
반면에 amount 값이 20인 경우에는 7페이지까지만 출력되어야 한다.
페이지 번호 이벤트 처리
화면에서 페이지 번호가 보이기는 하지만 아직 페이지 번호를 클릭했을 때 이벤트 처리가 남아있다.
우선 페이지와 관련된 태그의 href 속성값으로 페이지 번호를 가지도록 수정한다.
(번호의 출력 부분은
list.jsp
<c:if test="${pageMaker.prev}">
<li class="paginate_button previous">
<a href="${pageMaker.startPage-1}">Previous</a>
</li>
</c:if>
<c:forEach var="num" begin="${pageMaker.startPage}" end="${pageMaker.endPage}">
<li class="paginate_button ${pageMaker.cri.pageNum == num?"active":""}">
<a href="${num}">${num}</a>
</li>
</c:forEach>
<c:if test="${pageMaker.next}">
<li class="paginate_button next">
<a href="${pageMaker.endPage+1}">Next</a>
</li>
</c:if>
이제 화면에서는 태그는 href 속성값으로 단순히 번호만을 가지게 변경된다.

이 상태에서 페이지 번호를 클릭하게 되면 해당하는 URL이 존재하지 않기 때문에 문제가 생기게 된다.
태그가 원래의 동작을 못하도록 JS 처리를 한다.실제 페이지를 클릭하면 동작을 하는 부분은 별도의 <form> 태그를 이용해서 처리하도록 한다.
(
list.jsp
<form id="actionForm" action="../board/list" method="get">
<input type="hidden" name="pageNum" value="${pageMaker.cri.pageNum}">
<input type="hidden" name="amount" value="${pageMaker.cri.amount}">
</form>
기존에 동작하던 JS 부분은 아래와 같이 기존의 코드에 페이지 번호를 클릭하면 처리하는 부분이 추가된다.
list.jsp
<script type="text/javascript">
$(document).ready(
function () {
...
var actionForm = $("#actionForm");
$(".paginate_button a").on("click", function (e) {
e.preventDefault();
console.log('click');
actionForm.find("input[name='pageNum']").val($(this).attr("href"));
});
});
</script>
list.jsp에서는 <form> 태그를 추가해서 URL의 이동을 처리하도록 변경했다.JS에서는 태그를 클릭해도 페이지 이동이 없도록 preventDefault() 처리를 하고 <form> 태그 내 pageNum 값은 href 속성값으로 변경한다.이 처리를 하고나면
화면에서 페이지 번호를 클릭했을 때 <form> 태그 내의 페이지 번호가 바뀌는 것을 브라우저에서 개발자 도구를 통해 확인할 수 있다.

마지막 처리는 actionForm 자체를 submit() 시켜야 한다.
var actionForm = $("#actionForm");
$(".paginate_button a").on("click", function (e) {
e.preventDefault();
console.log('click');
actionForm.find("input[name='pageNum']").val($(this).attr("href"));
actionForm.submit();
});
조회 페이지로 이동
목록 화면에서 페이지 번호를 클릭하면 정상적으로 원하는 페이지로 이동하는 것을 볼 수 있지만 몇가지 문제가 있다.
우선 사용자가 3페이지에 있는 게시글을 클릭한 후 다시 목록으로 이동해 보면 무조건 1페이지 목록 페이지로 이동하는 현상이 있다.
페이징 처리를 하고나면 특정 게시물의 조회 페이지로 이동한 후 다시 목록으로 돌아가는 문제가 생긴다.
조회 페이지에서 List를 선택하면 다시 1페이지의 상태로 돌아가는 문제가 발생하는 것을 볼 수 있다.
이를 해결하기 위해서는 조회 페이지로 갈 때 현재 목록 페이지의 pageNum과 amount를 같이 전달해야 한다.
이런 경우 페이지 이동에 사용했던 <form> 태그에 추가로 게시물의 번호를 같이 전송하고
action 값을 조정해서 처리할 수 있다.
원래 게시물의 제목에는 /board/get?bno=숫자 로 이동할 수 있는 링크가 직접 처리되어 있었다.
list.jsp 참고
<td><a href='../board/get?bno=<c:out value="${board.bno}"/>'>
<c:out value="${board.title}"/><a/></td>
페이지 번호는 조회 페이지에 전달되지 않기 때문에 조회 페이지에서 목록 페이지로 이동할 때는 아무런 정보가 없이 다시 /board/list를 호출하게 된다.
간단하게는 각 게시물의 링크에 추가로 &pageNum=숫자 와 같이 처리할 수도 있지만 나중에 여러 조건들이 추가되는 상황에서는 복잡한 링크를 생성해야만 한다.
태그로 복잡한 링크를 생성하는 방식이 나쁘다고는 말할 수 없다.가장 대표적인 예가 검색엔진이다.검색엔진에는 출력된 정보와 링크를 저장해서 사용하기 때문에 태그 내의 링크가 완전한 URL인 경우가 노출에 유리하다.
만일 웹페이지가 검색엔진에 의해서 노출이 필요한 경우라면 직접 모든 문자열을 구성해 주는 방식이 더 좋다.
직접 링크로 연결된 경로를 페이지 이동과 마찬가지로 <form> 태그를 이용해서 처리할 것이므로 태그에는 이동하려는 게시물의 번호만을 가지게 수정한다.(이벤트 처리를 수월하게 하기 위해서 태그에 class 속성을 하나 부여한다.)
list.jsp
<td><a class="move" href='<c:out value="${board.bno}"/>'>
<c:out value="${board.title}"/></a>
</td>
화면에서는 조회 페이지로 가는 링크 대신에 단순히 번호만이 출력된다.

실제 클릭은 JS를 통해서 게시물의 제목을 클릭했을 때 이동하도록 이벤트 처리를 새로 작성한다.
list.jsp
$(".move").on("click", function (e) {
e.preventDefault();
actionForm.append("<input type='hidden' name='bno' value='" +
$(this).attr("href") + "'>')");
actionForm.attr("action", "../board/get");
actionForm.submit();
});
게시물의 제목을 클릭하면 <form> 태그에 추가로 bno 값을 전송하기 위해서 태그를 만들어 추가하고
``` get.jsp는 openForm이라는 id를 가진