elevne's Study Note
Pagination with Java & Javascript Ajax 본문
게시판과 같은 List들을 쭉 뽑아와야 할 때에는 거의 대부분 Pagination 작업이 필요하다. 한 화면에 전체 데이터를 불러와서 띄우게 되면 로딩 속도도 많이 느려지고, 보기에도 매우 불편하다.
Paging 처리를 하기 위해서는 ROW_NUM을 SELECT 문에서 가져올 필요가 있다. 쿼리는 아래와 같이 작성해줄 수 있다. (아래 예는 Oracle의 경우)
SELECT * FROM (
SELECT ROW_NUMBER() OVER(ORDER BY COL1) AS ROW_NUM
, COL2
, COL3
FROM TABLE1
)
WHERE ROW_NUM BETWEEN 0 AND 10
위와 같은 쿼리를 실행하게 되면 ROW_NUMBER() OVER()을 통해 ROW_NUM을, COL1 기준으로 가져오게 된다. 그럼 그 ROW_NUM을 기준으로 특정 값과 값 사이의 데이터들만 가져와줄 수 있는 것이다.
위 쿼리를 잘 활용하기 위해 Pagination을 위한 Java Class를 만들어줄 필요가 있다. 다음과 같이 Pagination.java 라는 이름의 파일을 만들어줄 수 있다.
package com.kr.lgcare.qrmanage.web.paging;
public class Pagination {
// 현재 페이지
private int currentPage;
// 페이지당 출력할 데이터 갯수(e.g., 10개씩, 20개씩...)
private int cntPerPage;
// 화면 하단 페이지 사이즈 (e.g., 1~10, 10~20 20~30)
private int pageSize;
// 전체 데이터의 개수
private int totalRecordCount;
// 전체 페이지의 개수
private int totalPageCount;
// 페이지 리스트의 첫 페이지 번호
private int firstPage;
// 페이지 리스트의 마지막 페이지 번호
private int lastPage;
// SQL의 조건절에 사용되는 첫 ROW_NUM
private int firstRecordIndex;
// SQL의 조건절에 사용되는 마지막 ROW_NUM
private int lastRecordIndex;
// 이전 페이지 존재 여부
private boolean hasPreviousPage;
// 다음 페이지 존재 여부
private boolean hasNextPage;
public Pagination(int currentPage, int cntPerPage, int pageSize) {
//강제입력방지
if (currentPage < 1) {
currentPage = 1;
}
// 10, 20, 30개 단위 이외 처리 방지
if (cntPerPage != 10 && cntPerPage != 20 && cntPerPage != 30) {
cntPerPage = 10;
}
// 하단 페이지 갯수 10개로 제한
if (pageSize != 10) {
pageSize = 10;
}
this.currentPage = currentPage;
this.cntPerPage = cntPerPage;
this.pageSize = pageSize;
}
public void setTotalRecordCount(int totalRecordCount) {
this.totalRecordCount = totalRecordCount;
if (totalRecordCount > 0) {
calculation();
}
}
private void calculation() {
// 전체 페이지 수 (현재 페이지 번호가 전체 페이지 수보다 크면 현재 페이지 번호에 전체 페이지 수를 저장)
totalPageCount = ((totalRecordCount - 1) / this.getCntPerPage()) + 1;
if (this.getCurrentPage() > totalPageCount) {
this.setCurrentPage(totalPageCount);
}
// 페이지 리스트의 첫 페이지 번호
firstPage = ((this.getCurrentPage() - 1) / this.getPageSize()) * this.getPageSize() + 1;
// 페이지 리스트의 마지막 페이지 번호 (마지막 페이지가 전체 페이지 수보다 크면 마지막 페이지에 전체 페이지 수를 저장)
lastPage = firstPage + this.getPageSize() - 1;
if (lastPage > totalPageCount) {
lastPage = totalPageCount;
}
// SQL의 조건절에 사용되는 첫 RNUM
firstRecordIndex = (this.getCurrentPage() - 1) * this.getCntPerPage();
// SQL의 조건절에 사용되는 마지막 RNUM
lastRecordIndex = this.getCurrentPage() * this.getCntPerPage();
// 이전 페이지 존재 여부
hasPreviousPage = firstPage == 1 ? false : true;
if(hasPreviousPage == false) {
if(currentPage != firstPage) {
hasPreviousPage = true;
}else {
hasPreviousPage = false;
}
}
// 다음 페이지 존재 여부
hasNextPage = (lastPage * this.getCntPerPage()) >= totalRecordCount ? false : true;
if(hasNextPage == false) {
//마지막 페이지에서 현재페이지가 마지막 페이지가 아닌경우 next처리
if(currentPage != lastPage) {
hasNextPage = true;
}else {
hasNextPage = false;
}
}
}
Getter Setter~~
}
위 처럼 필요한 값들을 설정 해줄 수 있을 뿐만 아니라, 다양한 함수를 만들어서 이 객체를 적절하게 활용할 수 있게끔 한다. 코드를 하나씩 읽어보면 어렵지 않게 이해할 수 있다.
Pagination 처리는 다양한 페이지에서 공통적으로 활용하는 경우가 많기 때문에 위처럼 객체화하여 사용하는 것이 편할 것이다. 나의 경우에는 이 Pagination 내 변수들에 Default 값들을 Setting 해준 후, 다른 DTO 객체를 활용하여 Query를 실행할 수 있도록 하였다. Controller는 아래와 같이 작성할 수 있었다.
@RequestMapping(value = "/search")
public String multiInputList(@ModelAttribute("searchDTO") SearchDTO dto, ModelMap model) throws Exception {
Pagination paginationInfo = new Pagination();
paginationInfo.setCurrentPageNo(dto.getPageIndex());
paginationInfo.setRecordCountPerPage(dto.getPageUnit());
paginationInfo.setPageSize(dto.getPageSize());
dto.setFirstIndex(paginationInfo.getFirstRecordIndex());
dto.setLastIndex(paginationInfo.getLastRecordIndex());
dto.setRecordCountPerPage(paginationInfo.getRecordCountPerPage());
// Service에서 만들어둔 검색 Function
List<ResultObj> resultList = boardService.searchList(dto);
if(resultList.size() == 0) {
paginationInfo.setTotalRecordCount(0);
} else {
paginationInfo.setTotalRecordCount(resultList.get(0).getRowCnt());
}
model.addAttribute("resultList", resultList);
model.addAttribute("paginationInfo", paginationInfo);
return "example/index";
}
MyBatis에서 SearchDTO 객체를 받아서 Query를 돌릴 수 있게끔 작성해두었다. model 안에 검색결과와 함께 pagination에 대한 정보도 같이 넘겨준다. 그럼 해당 정보들을 토대로 어렵지 않게 Pagination 관련 처리를 html 상에서 처리할 수 있을 것이다.
위 코드들을 토대로 간단한 페이징 처리를 해줄 수 있긴 하지만, Ajax로 검색을 하고 이를 페이징 처리해야 하는 경우에는 위와 같은 방법을 똑같이 사용할 수 없다. Ajax로 불러온 값들을 Paging 처리 해주기 위해 다음과 같은 함수를 하나 만들어준다.
function getPagination(function, paginationInfo){
var pagination = "";
var firstPageNo = 1;
var currentPageNo = paginationInfo.currentPageNo;
var firstPageNoOnPageList = paginationInfo.firstPageNoOnPageList;
var lastPageNoOnPageList = paginationInfo.lastPageNoOnPageList;
var pageSize = paginationInfo.pageSize;
var totalPageCount = paginationInfo.totalPageCount;
var lastPageNo = totalPageCount;
/* 총 페이지 수가 화면에 한 번에 표시하는 페이지 수보다 클 때 */
if (totalPageCount > pageSize) {
/* 첫 번째로 표시하는 페이지 넘버가 화면에 한 번에 표시하는 페이지 수보다 클 때 */
if (firstPageNoOnPageList > pageSize) {
/* 첫 번째 페이지로 이동하는 버튼 */
pagination += "<li><a href='?pageIndex=1' onclick='"+function+"(1); return false;'><i aria-hidden='true'></i></a></li>";
/* 1개 전 페이지로 이동하는 버트 */
pagination += "<li><a href='?pageIndex="+(firstPageNoOnPageList - 1)+"' onclick='"+function+"("+(firstPageNoOnPageList - 1)+"); return false;'>Prev</a></li>";
} else {
/* 그 외의 경우에는 두 버튼 다 1번 페이지로 이동하기 */
pagination += "<li><a href='?pageIndex=1' onclick='"+function+"(1); return false;'><i aria-hidden='true'></i></a></li>";
pagination += "<li><a href='?pageIndex=1' onclick='"+function+"(1); return false;'>Prev</a></li>";
}
}
/* 그 후 첫 번째 표시 페이지~마지막 표시페이지 버튼 추가해주기 */
for (var i = firstPageNoOnPageList; i <= lastPageNoOnPageList; i++) {
/* 현재 페이지를 누를 때는 아무것도 실행 안되게끔 */
if (i == currentPageNo) {
pagination += "<li><a onClick='return false;'>"+i+"</a></li>";
} else {
/* Pagination~ */
pagination += "<li><a href='?pageIndex="+i+"' onclick='"+function+"("+i+"); return false;'>"+i+"</a></li>";
}
}
/* 총 페이지 수가 화면에 한 번에 표시하는 페이지 수보다 클 때 */
if (totalPageCount > pageSize) {
/* 마지막 페이지 번호가 화면에 한 번에 표시하는 페이지 수보다 작을 때 */
if (lastPageNoOnPageList < totalPageCount) {
/* 다음 페이지로 이동 */
pagination += "<li><a href='?pageIndex="+(firstPageNoOnPageList + pageSize)+"' onclick='"+function+"("+(firstPageNoOnPageList + pageSize)+"); return false;'>Next</a></li>";
/* 마지막 페이지로 이동 */
pagination += "<li><a href='?pageIndex="+lastPageNo+"' onclick='"+function+"("+lastPageNo+"); return false;'><i aria-hidden='true'></i></a></li>";
} else {
pagination += "<li><a href='?pageIndex="+lastPageNo+"' onclick='"+function+"("+lastPageNo+"); return false;'>Next</a></li>";
pagination += "<li><a href='?pageIndex="+lastPageNo+"' onclick='"+function+"("+lastPageNo+"); return false;'><i aria-hidden='true'></i></a></li>";
}
}
return pagination;
}
위 함수는 function, paginationInfo 두 개의 인자를 받는다. 여기서 function에는 ajax를 통해 검색결과 리스트 및 Pagination 정보를 불러오는 함수를 넣어준다. 그리고 paginationInfo에는 그 함수를 실행시키고 얻은 data 안에 담긴 paginationInfo를 넣어주는 것이다. (위 함수는 ajax 사용 함수 success 부분 안에 들어가게 될 것임)
Ajax를 사용하여 리스트, Pagination 정보를 가져오는 함수는 다음과 같은 형식으로 작성할 수 있다.
function getData(pageNo){
// 결과 리스트 넣을 곳 초기화
$("#result").empty();
$("#resultPagination").empty();
// 검색어 가져오기
var tmpSearchTerm = $("#searchTerm").val();
// ajax 시작
$.ajax({
type: "POST",
url: "/searchAjax",
dataType: "json",
data: {
searchTerm : tmpSearchTerm,
pageIndex : pageNo
},
success: function(data){
if (data.resultList.length > 0){
for (var obj of data.resultList){
var trString = "";
trString += "<tr>";
trString += "<td>"+obj.id+"</td>";
trString += "<td>"+obj.title+"</td>";
trString += "<td>"+obj.content+"</td>";
trString += "<td>"+obj.date+"</td></tr>";
$("#result").append(trString);
}
$("#resultPagination").html(getPagination("getData", data.paginationInfo));
} else {
$("#result").append("<tr><td colspan='4'><br/>No Result For Search</td></tr>");
}
},
error: function(data){
alert("검색 실패");
}
});
}
위 코드 또한 어렵지 않게 이해할 수 있을 것이다. 위 함수를 보면 Ajax를 사용하고 있고, Paination을 처리해줄 부분에 위에서 만든 함수인 getPagination을 사용하고, 인자로 getData(위 함수 이름)과 ajax를 통해 가져온 paginationInfo 객체를 넘겨주고 있는 것을 확인할 수 있다. 마지막으로, Controller에서는 아래와 같이 처리해줄 수 있을 것이다.
@RequestMapping(value = "/searchAjax")
public ModelAndView setMultiRearingList(@ModelAttribute("searchDto") SearchDTO dto, Model model) throws Exception {
Pagination paginationInfo = new PaginationInfo();
paginationInfo.setCurrentPageNo(dto.getPageIndex());
paginationInfo.setRecordCountPerPage(dto.getPageUnit());
paginationInfo.setPageSize(dto.getPageSize());
dto.setFirstIndex(paginationInfo.getFirstRecordIndex());
dto.setLastIndex(paginationInfo.getLastRecordIndex());
dto.setRecordCountPerPage(paginationInfo.getRecordCountPerPage());
List<DataObj> resultList = searchService.search(dto);
if (resultList.size() == 0) {
paginationInfo.setTotalRecordCount(0);
} else {
paginationInfo.setTotalRecordCount(resultList.get(0).getRowCnt());
}
model.addAttribute("resultList", resultList);
model.addAttribute("paginationInfo", paginationInfo);
ModelAndView modelAndView = new ModelAndView("jsonView", model);
return modelAndView;
}
'Frontend > JS & React' 카테고리의 다른 글
웹 개발 공부 (onKeyup, JS URLSearchParams) (0) | 2022.11.17 |
---|---|
웹 개발 공부 (Smart Editor, Ondblclick, jquery prev/next, Sticky) (0) | 2022.11.14 |
JQuery 반복문으로 Row별 값 Setting 해보기 (0) | 2022.10.21 |
자바스크립트 비동기처리 (0) | 2022.10.19 |
jQuery Selector 다루기 (0) | 2022.10.14 |