오늘은 게시판 만들 때 꼭 필요한 페이지 나누는 기능을 포스팅해보겠습니다!
■ 페이지네이션
웹 사이트에는 게시판을 비롯한 여러 정보들을 페이지 단위로 보여줍니다.
따라서 원하는 자료가 있는 페이지를 바로가기하기 위해서는 페이지 하단에 위치한
[이전] 1 2 3 4 5 [다음]
과 같은 페이지 연결 링크가 필요합니다.
이를 페이지내비게이션 또는 페이지네이션(pagination)이라고 합니다.
■ 페이지 나누기 공식
페이지를 나누려면 먼저 공식을 대입해야 합니다.
만약 전체 게시물 수 991개라면 페이지당 게시물 수를 10개로 표기하고자 할 때 몇 페이지를 표시해야 할까요?
99페이지가 아닌 100페이지를 표시해야 합니다.
991개의 페이지를 10으로 나누면 99.1입니다. 여기서 0.1도 1페이지이기 때문에 올림 처리 해주셔야 합니다.
계산 ) 991/10 => 99.1
나머지 공식들은 간단하게 아래에 정리했습니다.
페이지 시작번호, 끝번호 계산
1페이지 => 1 ~ 10 (게시물 번호 rownum으로 처리)
2페이지 => 11 ~ 20
. . .
11페이지 => 101 ~ 110
57페이지 => 561 ~ 570
99페이지 => 981 ~ 990
100페이지 => 991 ~ 1000
게시물의 시작번호=(현재페이지 - 1 ) * 페이지당 게시물수 + 1
1페이지 => (1-1) * 10 + 1 => 1
2페이지 => (2-1) * 10 + 1 => 11
7페이지 => (7-1) * 10 + 1 => 61
게시물의 끝번호=시작번호 + ( 페이지당 게시물수 – 1 )
1페이지 => 1 + ( 10 – 1 ) => 10
2페이지 => 11 + ( 10 – 1 ) => 20
전체 페이지 블록수
전체페이지개수 / 10
31 / 10 => 3.1 => 4개 블록
- 페이지 자체도 끊어서(블록처리) 표시해줘야 한다.
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
현재 페이지가 속한 블록
(현재페이지-1)/페이지블록단위 + 1
10페이지를 하나의 블록단위로 한다면
1페이지 => 몇 번째 블록? 1
(1-1)/10 + 1 =>1
9페이지 => 1블록
(9-1)/10 + 1 => 1블록
11페이지 => 2블록
(11-1)/10 + 1 =>2블록
27페이지
(27-1)/10 + 1 =>3블록
페이지 블록의 시작번호
(현재블록-1)*블록단위 + 1
1블록 => (1-1)*10 + 1 => 1
2블록 => (2-1)*10 + 1 => 11
3블록 => (3-1)*10 + 1 => 21
페이지 블록의 끝번호
블록시작번호+(페이지블록단위 - 1)
1블록 => 1+(10-1) => 10
2블록 => 11+(10-1) => 20
6블록 => 51+(10-1) => 60
■ 응용 예제
페이지네이션을 알아보기 위해 먼저 사원 991개 데이터를 추가했습니다.
select*from test;
create table test as select *from emp where 1=0;
declare--선언부
i number := 1; --i변수에 1을 대입
begin -- 실행부
while i<=991 loop
insert into test(empno,ename) values
(i,'사원'||i);
i := i+1;
end loop;
end;
/
데이터를 추가하고 이제 페이지를 나눠야 하는데, 페이지를 나누려면 rownum 이 필요합니다. rownum은 연속된 범위의 값을 처리해 줍니다.
그렇다고 rownum만 쓰면 안 되고, 서브 쿼리를 써야 합니다.
select *
from(
--전체 레코드에 일련번호 부여(2)
select A.*,rownum as rn
from(
--전체 레코드를 뽑음(1)
select empno, ename
from test
order by empno
)A
)
where rn between 21 and 30;
위 코드는 서브쿼리를 사용해서 21페이지부터 30페이지를 출력하는 코드입니다.
출력된 결과를 보면 21번부터 30번까지 출력되는 걸 확인할 수 있는데, 그렇다면 SQL 문을 mapper 페이지에서 적용할 땐 between #{함수} and #{함수}를 넣어주면 됩니다.
SQL 작업이 끝났다면 COMMIT을 해줘야 합니다.
웹단에 데이터가 안 나올 수 있으니 꼭 해주고 넘어가야 합니다.
그럼 index 페이지부터 View 단 페이지까지 알아봅시다.
index page
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>index</title>
<script src="../include/jquery-3.6.3.min.js"></script>
<%@ include file="../include/header.jsp" %>
<script type="text/javascript">
$(document).ready(function() {
list('1'); //1페이지란 뜻
});
function list(curPage) {
var param = "curPage="+curPage
$.ajax({
type:"post",
url:"${path}/page_servlet/list.do",
data:param,
success: function(result) {
$("#result").html(result);
}
});
}
</script>
</head>
<body>
<h2>페이지 나누기</h2>
<div id="result"></div>
</body>
</html>
DTO
package page;
public class EmpDTO {
private int empno;
private String ename;
//getter/setter , toString , 생성자
public int getEmpno() {
return empno;
}
public void setEmpno(int empno) {
this.empno = empno;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
@Override
public String toString() {
return "EmpDTO [empno=" + empno + ", ename=" + ename + "]";
}
public EmpDTO(int empno, String ename) {
this.empno = empno;
this.ename = ename;
}
public EmpDTO() {
}
}
DTO 페이지까지 만들고 페이지네이션을 하려면 위에서 말씀드린 것처럼 페이지 처리 공식이 필요합니다.
공식 페이지는 아래 코드를 사용하셔도 됩니다.
Pager
package page;
public class Pager {
public static final int PAGE_SCALE=10;//페이지당 게시물수(10개)
public static final int BLOCK_SCALE=10;//페이지 블록 개수
private int curPage; //현재 페이지
private int prevPage; //이전 페이지
private int nextPage; //다음 페이지
private int totPage; //전체 페이지 개수
private int totBlock; //전체 페이지블록 개수
private int curBlock; //현재 페이지블록
private int prevBlock; //이전 페이지블록
private int nextBlock; //다음 페이지블록
private int pageBegin; // #{start}에 전달될 값
private int pageEnd; // #{end}에 전달될 값
private int blockStart; //페이지블록의 시작페이지 번호
private int blockEnd; //페이지블록의 끝페이지 번호
//getter, setter만 생성, 단 상수 2개는(PAGE_SCALE, BLOCK_SCALE) 빼고 만듦
// Pager(레코드개수, 보여줄 페이지번호)
public Pager(int count, int curPage){
curBlock=1; //페이지블록을 1로 초기화
this.curPage=curPage;
setTotPage(count); //전체 페이지 개수 계산
setPageRange(); // #{start}, #{end} 값 계산
setTotBlock(); // 페이지블록의 갯수 계산
setBlockRange(); //페이지블록의 범위 설정
}
//페이지블록의 범위 설정
public void setBlockRange(){
//현재페이지가 몇 번째 페이지블록에 속하는지 계산
//(현재페이지-1)/페이지블록단위 + 1
curBlock=(int)Math.ceil((curPage-1) / BLOCK_SCALE)+1;
//(현재블록-1)*블록단위+1
blockStart=(curBlock-1) * BLOCK_SCALE + 1;
//블록시작번호+(페이지블록단위-1)
blockEnd=blockStart + (BLOCK_SCALE-1);
//블록의 마지막 페이지번호가 범위를 초과하지 않도록 처리
if(blockEnd > totPage){
blockEnd = totPage;
}
//[이전] 11 12 13 14 15 16 17 18 19 20 [다음]
//[이전]을 눌렀을 때 이동할 페이지, 현재블록이 1이면 1로 감, [이전] 이란표시는 빼야 함
prevPage=curBlock==1 ? 1 : (curBlock-1) * BLOCK_SCALE;
//[다음]을 눌렀을 때 이동할 페이지
nextPage=curBlock>totBlock
? (curBlock*BLOCK_SCALE) : (curBlock*BLOCK_SCALE)+1;
if(nextPage >= totPage){//다음페이지가 토털페이지보다 크면 [다음] 표시 뺌
nextPage = totPage;
}
}
public void setPageRange(){
//시작번호=(현재페이지 - 1 ) * 페이지당 게시물수 + 1
pageBegin = (curPage - 1) * PAGE_SCALE + 1;
//끝번호=시작번호 + ( 페이지당 게시물수 – 1 )
pageEnd = pageBegin + (PAGE_SCALE - 1);
}
public int getCurPage() {
return curPage;
}
public void setCurPage(int curPage) {
this.curPage = curPage;
}
public int getPrevPage() {
return prevPage;
}
public void setPrevPage(int prevPage) {
this.prevPage = prevPage;
}
public int getNextPage() {
return nextPage;
}
public void setNextPage(int nextPage) {
this.nextPage = nextPage;
}
public int getTotPage() {
return totPage;
}
//전체 페이지 개수 계산
public void setTotPage(int count) {
// 991 / 10 => 99.1 올림 => 100
// 991/10 => 99
// 991.0/10 => 99.1
//ceil() 올림, round() 반올림, floor() 버림
totPage = (int)Math.ceil(count * 1.0 / PAGE_SCALE);
}
public int getTotBlock() {
return totBlock;
}
//페이지블록의 개수 계산
public void setTotBlock() {
// 991페이지 : 991/10 => 99, 한 페이지 누락이기 때문에 올림(ceil) 처리함
totBlock = (int)Math.ceil(totPage * 1.0 / BLOCK_SCALE);
}
public int getCurBlock() {
return curBlock;
}
public void setCurBlock(int curBlock) {
this.curBlock = curBlock;
}
public int getPrevBlock() {
return prevBlock;
}
public void setPrevBlock(int prevBlock) {
this.prevBlock = prevBlock;
}
public int getNextBlock() {
return nextBlock;
}
public void setNextBlock(int nextBlock) {
this.nextBlock = nextBlock;
}
public int getPageBegin() {
return pageBegin;
}
public void setPageBegin(int pageBegin) {
this.pageBegin = pageBegin;
}
public int getPageEnd() {
return pageEnd;
}
public void setPageEnd(int pageEnd) {
this.pageEnd = pageEnd;
}
public int getBlockStart() {
return blockStart;
}
public void setBlockStart(int blockStart) {
this.blockStart = blockStart;
}
public int getBlockEnd() {
return blockEnd;
}
public void setBlockEnd(int blockEnd) {
this.blockEnd = blockEnd;
}
}
Controller
package page;
import java.io.IOException;
import java.util.List;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/page_servlet/*")
public class PageController extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String uri = request.getRequestURI();
EmpDAO dao = new EmpDAO();
if(uri.indexOf("list.do") != -1) {
int count = dao.empCount();//레코드 개수 계산
int curPage = 1; //null일 때는 기본값 1을 줌
if(request.getParameter("curPage") != null) {
curPage = Integer.parseInt(request.getParameter("curPage"));
}
Pager pager = new Pager(count, curPage);
int start=pager.getPageBegin();
int end =pager.getPageEnd();
List<EmpDTO> list = dao.empList(start,end);
request.setAttribute("list", list);
//페이지 내비게이션에 필요한 정보 전달
request.setAttribute("page", pager);
//포워딩
String page = "/page/list.jsp";
RequestDispatcher rd = request.getRequestDispatcher(page);
rd.forward(request, response);
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
DAO
package page;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import sqlmap.MybatisManager;
public class EmpDAO {
public int empCount() {
SqlSession session = MybatisManager.getInstance().openSession();
int count = session.selectOne("emp.empCount");
session.close();
return count;
}
public List<EmpDTO> empList(int start, int end){
SqlSession session = MybatisManager.getInstance().openSession();
Map<String, Object> map = new HashMap<>();
map.put("start", start);
map.put("end", end);
System.out.println(map);
List<EmpDTO> items = session.selectList("emp.empList" , map);
session.close();
return items;
}
}
DAO에서 mapper 파일로 id 값을 보냈습니다. 그럼 SQL문을 전달하려면 먼저 mapper 등록을 해야 합니다.
(이 부분은 Mybatis를 사용하는 부분이라서 궁금하신 개발자님들은 아래 페이지 참고해 보시면 도움이 될 거예요!)
mapper 등록
<mapper resource="/page/mapper/emp.xml"/>
mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="emp">
<select id="empList" resultType="e">
select *
from(
select A.*,rownum as rn
from(
select empno, ename
from test
order by empno
)A
)
where rn between #{start} and #{end}
</select>
<select id="empCount" resultType="int">
select count(*) from test
</select>
</mapper>
위에서 언급한 것처럼 where 절에 해당 페이지를 빼고 함수를 추가합니다.
마지막으로 Controller에서 보내주는 View 단 페이지를 생성해서 결과를 확인해 봅시다.
View 단
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>list</title>
<script src="../include/jquery-3.6.3.min.js"></script>
<%@ include file="../include/header.jsp" %>
<style type="text/css">
table {
width: 100%;
border: 1px solid black;
border-collapse: collapse;
}
th, td{
border: 1px solid black;
}
</style>
</head>
<body>
<table>
<tr>
<th>사번</th>
<th>이름</th>
</tr>
<c:forEach var = "row" items="${list}">
<tr>
<td>${row.empno}</td>
<td>${row.ename}</td>
</tr>
</c:forEach>
<tr>
<td colspan="2" align="center">
<!-- onclick시 index.jsp의 자동 호출되었던 메모리에 기억하고 있는 function list(curPage)를 호출한다. -->
<c:if test="${page.curPage > 1 }">
<a href="#" onclick="list('1')">[처음]</a>
</c:if>
<c:if test="${page.curBlock > 1 }">
<a href="#" onclick="list('${page.prevPage}')">[이전]</a>
</c:if>
<c:forEach var="num" begin="${page.blockStart}" end="${page.blockEnd}">
<c:choose>
<c:when test="${num == page.curPage}">
<span style="color: red">${num}</span>
</c:when>
<c:otherwise>
<a href="#" onclick="list('${num}')">${num}</a>
</c:otherwise>
</c:choose>
</c:forEach>
<c:if test="${page.curBlock < page.totBlock}">
<a href="#" onclick="list('${page.nextPage}')">[다음]</a>
</c:if>
<c:if test="${page.curPage < page.totPage}">
<a href="#" onclick="list('${page.totPage}')">[끝]</a>
</c:if>
</td>
</tr>
</table>
</body>
</html>
■ 예제 출력 결과
마치며
오늘까지 페이지 처리하는 기능에 대해서 알아봤습니다.
그럼 실질적인 게시판을 만들어 봐야 하는데,
설렘 반 불안 반이네요...^^
'[ View ] > JSP' 카테고리의 다른 글
[ JSP ] 게시판 만들기 1 - DB 설정 및 게시판 목록 구현 (0) | 2023.03.28 |
---|---|
[ JSP ] 파일 업로드 (0) | 2023.03.23 |
[ JSP ] Mybatis 응용 예제(방명록) (0) | 2023.03.19 |
[ JSP ] Mybatis 응용 예제(한 줄 메모장) (0) | 2023.03.17 |
[ JSP ] Mybatis 개념 및 설정 (0) | 2023.03.16 |