MyBatis :: Paging 페이징 구현
1. com.paging 패키지에 들어가는 Paging.java 소스
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150 |
package com.paging;
public class Paging {
int recordsPerPage; // 페이지당 레코드 수
int firstPageNo; // 첫번째 페이지 번호
int prevPageNo; // 이전 페이지 번호
int startPageNo; // 시작 페이지 (페이징 너비 기준)
int currentPageNo; // 페이지 번호
int endPageNo; // 끝 페이지 (페이징 너비 기준)
int nextPageNo; // 다음 페이지 번호
int finalPageNo; // 마지막 페이지 번호
int numberOfRecords; // 전체 레코드 수
int sizeOfPage; // 보여지는 페이지 갯수 (1,2,3,4,5 갯수)
public Paging(int currentPageNo, int recordsPerPage) {
this.currentPageNo = currentPageNo;
//기본 페이지 : 5개 보기를 default로 설정함
this.sizeOfPage = 5;
//recordsPerPage가 0이 아니면 recordsPerPage, 아니면 무조건 5(default : 5)
this.recordsPerPage = (recordsPerPage != 0) ? recordsPerPage : 5;
// System.out.println("debug > paging sizeOfPage : " + this.sizeOfPage);
// System.out.println("debug > paging recordsPerPage : " + this.recordsPerPage);
// System.out.println("debug > paging currentPageNo : " + this.currentPageNo);
}
public int getRecordsPerPage() {
return recordsPerPage;
}
public void setRecordsPerPage(int recordsPerPage) {
this.recordsPerPage = recordsPerPage;
}
public int getFirstPageNo() {
return firstPageNo;
}
public void setFirstPageNo(int firstPageNo) {
this.firstPageNo = firstPageNo;
}
public int getPrevPageNo() {
return prevPageNo;
}
public void setPrevPageNo(int prevPageNo) {
this.prevPageNo = prevPageNo;
}
public int getStartPageNo() {
return startPageNo;
}
public void setStartPageNo(int startPageNo) {
this.startPageNo = startPageNo;
}
public int getCurrentPageNo() {
return currentPageNo;
}
public void setCurrentPageNo(int currentPageNo) {
this.currentPageNo = currentPageNo;
}
public int getEndPageNo() {
return endPageNo;
}
public void setEndPageNo(int endPageNo) {
this.endPageNo = endPageNo;
}
public int getNextPageNo() {
return nextPageNo;
}
public void setNextPageNo(int nextPageNo) {
this.nextPageNo = nextPageNo;
}
public int getFinalPageNo() {
return finalPageNo;
}
public void setFinalPageNo(int finalpageNo) {
this.finalPageNo = finalpageNo;
}
public int getNumberOfRecords() {
return numberOfRecords;
}
public void setNumberOfRecords(int numberOfRecords) {
this.numberOfRecords = numberOfRecords;
}
/**
* 페이징 생성
*/
public void makePaging() {
if (numberOfRecords == 0) // 게시글 전체 수가 없는 경우
return;
if (currentPageNo == 0)
setCurrentPageNo(1); // 기본 값 설정
if (recordsPerPage == 0)
setRecordsPerPage(10); // 기본 값 설정
// 마지막 페이지
int finalPage = (numberOfRecords + (recordsPerPage - 1)) / recordsPerPage;
if (currentPageNo > finalPage)
setCurrentPageNo(finalPage);// 기본 값 설정
if (currentPageNo < 0 || currentPageNo > finalPage)
currentPageNo = 1; // 현재 페이지 유효성 체크
// 시작 페이지 (전체)
boolean isNowFirst = currentPageNo == 1 ? true : false;
boolean isNowFinal = currentPageNo == finalPage ? true : false;
int startPage = ((currentPageNo - 1) / sizeOfPage) * sizeOfPage + 1;
int endPage = startPage + sizeOfPage - 1;
if (endPage > finalPage)
endPage = finalPage;
setFirstPageNo(1); // 첫번째 페이지 번호
if (isNowFirst)
setPrevPageNo(1); // 이전 페이지 번호
else // 이전 페이지 번호
setPrevPageNo(((currentPageNo - 1) < 1 ? 1 : (currentPageNo - 1)));
setStartPageNo(startPage); // 시작페이지
setEndPageNo(endPage); // 끝 페이지
if (isNowFinal)
setNextPageNo(finalPage); // 다음 페이지 번호
else
setNextPageNo(((currentPageNo + 1) > finalPage ? finalPage : (currentPageNo + 1)));
setFinalPageNo(finalPage); // 마지막 페이지 번호
}
} |
cs |
Paging.java 클래스 생성자에서 기본적으로 받아와야 할 값은
currentPageNo(현재 페이지), recordsPerPage(페이지당 게시글 수) 이렇게 두 인자가 필요하다.
생성자에서 인자 두 개를 받아 controller에서 컬럼의 인덱스를 리턴하는 offset 변수를 선언하는데 쓰게 된다.
2. controller (Servlet)
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106 |
package com.servlet.book;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.paging.Paging;
import com.service.BookManageService;
import com.vo.BookVO;
@SuppressWarnings("serial")
@WebServlet("/BookCheckOutAdmin")
public class BookCheckOutAdminServlet extends HttpServlet {
public BookCheckOutAdminServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Paging Calculation
int currentPageNo = 1;
int recordsPerPage = 0;
String url = null;
//커넥션풀 연결, 인스턴스 생성
BookManageService bookManageService = BookManageService.getInstance();
//pages, lines 파라미터를 받아 currentPageNo, recordsPerPage 대입
//처음 페이지 열릴 때에는 당연히 1, 0임
if (request.getParameter("pages") != null)
currentPageNo = Integer.parseInt(request.getParameter("pages"));
if (request.getParameter("lines") != null)
recordsPerPage = Integer.parseInt(request.getParameter("lines"));
//Paging 객체 생성(currentPagenNo, recordsPerPage를 인자로 넣고 초기화함)
//객체 선언한 뒤 paging 출력해보면 recordsPerPage가 5로 나온다.
Paging paging = new Paging(currentPageNo, recordsPerPage);
//System.out.println("debug > BookCheckOutAdminServlet paging : " + paging);
//해당 게시글의 인덱스를 구하는 변수(offset)
int offset = (paging.getCurrentPageNo() - 1) * paging.getRecordsPerPage();
//System.out.println("debug > BookCheckOutAdminServlet offset : " + offset);
//현재 대출 가능한 도서(book_lending_possible이 true인 목록만 가져옴)
List<BookVO> bookList = bookManageService.selectAllLendingPossibleBook(offset,
paging.getRecordsPerPage());
//System.out.println("debug > BookCheckOutAdminServlet bookList : " + bookList);
//bookList 전체 갯수 구하여 numberOfRecords 메소드에 셋팅함
paging.setNumberOfRecords(bookManageService.getBookDao().getNoOfRecords());
//System.out.println("debug > BookCheckOutAdminServlet numberOfRecords : "
// paging.getNumberOfRecords);
//페이징 만듦
paging.makePaging();
//bookList가 존재할 경우
if (bookList != null) {
//book_check_out_list.jsp에서 출력할 객체들을 request 단위로 설정함
request.setAttribute("bookList", bookList);
request.setAttribute("paging", paging);
request.setAttribute("servletPath", "BookCheckOutAdmin");
//System.out.println("debug > BookCheckOutAdminServlet attribute bookList : "
// + request.getAttribute("bookList"));
//System.out.println("debug > BookCheckOutAdminServlet attribute paging : "
// + request.getAttribute("paging"));
//System.out.println("debug > BookCheckOutAdminServlet servletPath : "
// + request.getAttribute("servletPath"));
url = "book_check_out_list.jsp";
}
//list가 존재하지 않을 경우
else {
//error_msg.jsp에서 출력할 객체들을 request 단위로 설정함
request.setAttribute("msg", "Error 가 발생했습니다.");
//System.out.println("debug > BookCheckOutAdminServlet attribute msg : "
// + request.getAttribute("msg"));
url = "error_msg.jsp";
}
//해당 url로 모든 request, response 정보 넘김
//http 창에서 url이 이동하는 url로 변하진 않음
request.getRequestDispatcher(url).forward(request, response);
}
}
|
cs |
페이징을 할 때에는 SELECT 쿼리문에서 LIMIT 를 사용하기 때문에
LIMIT에 들어갈 컬럼의 인덱스 값과 SELECT할 컬럼의 갯수가 필요하다.
currentPage(현재 페이지), recordsPerPage로 설정해 놓고 Paging 객체 생성자에 두 값을 넣어
컬럼의 인덱스(offset)를 도출한다.
* 컬럼의 index 구하기 : (현재 페이지 - 1 ) * 페이지당 SELECT할 컬럼 수
편의에 맞게 '~개 보기'로 하여 recordsPerPage의 값이 변할 수 있다.
* 페이징을 할 때 꼭 알아야 할 값 (중요) *
- 현재 페이지
- 한 페이지 당 출력할 컬럼 갯수
- 전체 컬럼 수
전체 컬럼 수를 알아야 페이지가 몇 개가 나올 것인지를 알 수 있기 때문에 전체 컬럼 수는 꼭 필요하다.
그러나 컬럼은 편의에 따라 추가하거나 삭제될 수 있기 때문에
SELECT 쿼리문으로 해당 list 를 구한 후 COUNT를 도출해야만 한다.
(list를 구한 후 bookDAO.getNoOfRecords() 메소드를 이용하여 numberOfRecords를 setter한 뒤,
페이징을 만드는 메소드인 paging.makepaging() 을 실행하였음)
또한 반드시 setAttribute로 설정해줘야 list.jsp에서 paging 파라미터들을 사용할 수 있다.
3. Service
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 |
package com.service;
import java.util.List;
import com.dao.BookDao;
import com.vo.BookVO;
public class BookManageService {
static BookManageService bookManageService = null;
BookDao bookDao;
BookRentInfoDao bookRentInfoDao;
public BookManageService() {
bookDao = BookDao.getInstance();
} //BookDao를 리턴하는 getBookDao 메소드
public BookDao getBookDao() {
return bookDao;
}
public static BookManageService getInstance() {
if (bookManageService == null) {
bookManageService = new BookManageService();
}
return bookManageService;
}
public List<BookVO> selectAllLendingPossibleBook(int offset, int noOfRecords) {
return bookDao.selectAllLendingPossibleBook(offset, noOfRecords);
}
} |
cs |
Controller에서 Service의 selectAllLendingPossibleBook 메소드를 호출한 뒤
Service 클래스에서 DAO 클래스를 호출한다.
디폴트 생성자를 통해 인스턴스를 초기화했다.
* Service 클래스 *
- DAO 클래스를 바로 호출하지 않고, Service가 DAO를 호출하게 함.
- 1개의 Service는 1개 이상의 DAO 클래스를 실행 가능하게 한다.
(대형 프로젝트에서는 1개의 Service가 3개 이상의 DAO를 실행 가능하게 하기도 함)
- 메뉴 1개당 보통 Servlet 1개, DAO 1개, Service 1개, sql 1개가 존재함.
- 객체지향에 따라 역할에 맞게 분류함.
4. DAO
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
59
60 |
package com.dao;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.log4j.Logger;
import com.mybatis.MyBatisConnectionFactory;
import com.vo.BookVO;
public class BookDao implements BookDaoImpl {
static BookDao bookDao;
SqlSessionFactory sqlSessionFactory;
final static Logger log = Logger.getLogger(BookDao.class);
int noOfRecords;
public BookDao(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
public int getNoOfRecords() {
return noOfRecords;
}
@Override
public List<BookVO> selectAllLendingPossibleBook(int offset, int noOfRecords) {
// log.debug("selectAllLendingPossibleBook offset : " + offset +
// " , noOfRecords : " + noOfRecords);
List<BookVO> bookList = new ArrayList<BookVO>();
SqlSession session = sqlSessionFactory.openSession();
HashMap<String, Object> params = new HashMap<String, Object>();
params.put("offset", offset);
params.put("noOfRecords", noOfRecords);
// log.debug("selectAllLendingPossibleBook params : " + params);
try {
bookList = session.selectList("BookDAO.selectAllLendingPossibleBook", params);
this.noOfRecords = session.selectOne("BookDAO.selectTotalRecords");
// log.debug("selectAllLendingPossibleBook bookList : " + bookList);
// log.debug("selectAllLendingPossibleBook noOfRecords : " + this.noOfRecords);
} finally {
session.close();
}
return bookList;
} |
cs |
DAO 클래스에서 offset, noOfRecords 인자를 받아 쿼리를 구현한다.
받은 인자들을 쿼리문으로 보낼 때 HashMap을 쓴 이유는
MyBatis에서 인자를 2개 이상 보내기 위한 방법은 HashMap 밖에 없기 때문이다.
보내야 할 인자가 1개면 단순하게 변수를 보내면 끝이지만,
2개 이상인 경우 반드시 HashMap 객체를 써야 한다.
쿼리를 bookList 인스턴스에 담은 후
전체 bookList 갯수를 구하는 쿼리를 실행하여 noOfRecords 변수를 초기화한다.
(일전에 앞에서 list를 구한 뒤 count를 구해야 한다고 앞서 적은 바 있다.)
5. DAOImpl
1
2
3
4
5
6
7
8
9
10 |
package com.dao;
import java.util.List;
import com.vo.BookVO;
public interface BookDaoImpl {
List<BookVO> selectAllLendingPossibleBook(int offset, int noOfRecords);
} |
cs |
6. mapper.xml
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 |
<mapper namespace="BookDAO">
<resultMap id="resultBook" type="BookVO">
<result property="bookCode" column="book_code" />
<result property="bookName" column="book_name" />
<result property="bookAuthor" column="book_author" />
<result property="bookMade" column="book_made" />
<result property="bookPrice" column="book_price" />
<result property="bookLendingPossible" column="book_lending_possible" />
<result property="bookLendingCount" column="book_lending_count" />
</resultMap>
<select id="selectAllLendingPossibleBook" parameterType="hashmap" resultMap="resultBook">
SELECT SQL_CALC_FOUND_ROWS
book_code,
book_name,
book_author,
book_made,
book_price,
book_lending_count
FROM book
WHERE book_lending_possible is true
LIMIT #{offset}, #{noOfRecords}
</select>
<select id="selectTotalRecords" resultType="int">
SELECT FOUND_ROWS();
</select>
</mapper> |
cs |
FOUND_ROWS() : total count를 구할 때 한 번의 쿼리만으로 처리 가능.
count를 쓰면 두 번의 쿼리를 날려야 하므로 과부하를 유발한다.
SQL_CALC_FOUND_ROWS를 쓰고 FOUND_ROWS() 를 쓰면
앞 쿼리에 썼던 SELECT한 전체 갯수를 FOUND_ROWS()를 쓴 쿼리에서 반환한다.
(이전 쿼리에 LIMIT가 있는 경우, LIMIT 부분은 무시하고 SELECT된 전체 갯수를 반환한다.)
FOUND_ROWS()로 전체 갯수를 구하는 이유 : 페이징의 필수 요소이기 때문에
(실제로 DAO 클래스에서 쿼리 부분을 커멘트 하면 페이징이 되지 않음)
7. import 될 paging.jsp
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 |
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div class="paginate">
<c:if test="${param.currentPageNo ne param.firstPageNo}">
<a href="javascript:goPage('${servletPath}', ${param.prevPageNo}, ${param.recordsPerPage})" class="prev">이전</a>
</c:if>
<span>
<c:forEach var="i" begin="${param.startPageNo}" end="${param.endPageNo}" step="1">
<c:choose>
<c:when test="${i eq param.currentPageNo}">
<b><font size=+1>
<a href="javascript:goPage('${servletPath}', ${i}, ${param.recordsPerPage})" class="choice">${i}</a>
</font>
</b>
</c:when>
<c:otherwise>
<a href="javascript:goPage('${servletPath}', ${i}, ${param.recordsPerPage})">${i}</a>
</c:otherwise>
</c:choose>
</c:forEach>
</span>
<c:if test="${param.currentPageNo ne param.finalPageNo}">
<a href="javascript:goPage('${servletPath}', ${param.nextPageNo}, ${param.recordsPerPage})" class="next">다음</a>
</c:if>
</div> |
cs |
8. paging.jsp 에서 이동하는 javascript goPage 함수
1
2
3
4
5
6 |
function goPage(url, pages, lines) {
url += '?' + "pages=" + pages + "&lines=" + lines;
location.href = url;
} |
cs |
9. list.jsp
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 |
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>main</title>
<script type="text/javascript" src="./js/script.js"></script>
</head>
<body>
<jsp:include page="paging.jsp" flush="true">
<jsp:param name="servletPath" value="${servletPath}" />
<jsp:param name="recordsPerPage" value="${paging.recordsPerPage}" />
<jsp:param name="firstPageNo" value="${paging.firstPageNo}" />
<jsp:param name="prevPageNo" value="${paging.prevPageNo}" />
<jsp:param name="startPageNo" value="${paging.startPageNo}" />
<jsp:param name="currentPageNo" value="${paging.currentPageNo}" />
<jsp:param name="endPageNo" value="${paging.endPageNo}" />
<jsp:param name="nextPageNo" value="${paging.nextPageNo}" />
<jsp:param name="finalPageNo" value="${paging.finalPageNo}" />
</jsp:include>
</body>
</html> |
cs |
paging.jsp로 작성한 소스를 jsp 액션 태그 include로 불러오고,
Controller에서 request 단위로 setAttribute 설정한 paging 객체를 불러온다.
실제로 프로젝트를 구현하면 위와 같이 페이징이 되고,
recordsPerPage 값을 몇으로 설정하냐에 따라서 SELECT 쿼리문에 들어가는 LIMIT의 값이 바뀌게 된다.
(option 값 변화에 따라 LIMIT가 바뀌는 부분은 본문에 넣지 않았다.)