Spring/Maven Project

Spring에 Paging(페이징) 처리 및 적용

carrotweb 2022. 12. 11. 19:52
728x90
반응형

게시판에서 게시물 리스트를 가져올 때 가져올 게시물 리스트 수를 지정하여 가져오지 않으면 전체 게시물(수십 건부터 수백만 건)을 리스트를 가져오게 됩니다. 그러면 Query(쿼리) 처리로 인해 Database(데이터베이스)에 부하가 발생하고 많은 처리 결과를 리턴하게 되어 네트워크의 트래픽이 증가되는 문제가 발생하게 됩니다. 그래서 게시판에서 게시물 리스트를 가져올 때 게시물 리스트 수를 정해서 나누어서 가져오는 게 좋습니다.

 

이처럼 일정한 크기로 Database(데이터베이스)의 Table(테이블)에서 Row(레코드)를 나누는 것을 Paging(페이징)이라고 하고 일정한 크기를 가진 Row(레코드)를 Page(페이지)라고 합니다. 그리고 나누어진 Page(페이지)를 이전 페이지나 다음 페이지 또는 특정 페이지로 이동할 수 있게 처리하는 것을 Pagination(페이지네이션)이라고 한다.

 

이번에는 Paging(페이징)을 처리하여 지정된 수만큼 게시물 리스트를 가져오도록 하겠습니다.

 

Paging(페이징) 처리를 위해서는 SELECT 된 결과에 대한 전체 레코드 수, 페이지 크기(한 번에 가져올 레코드 수), 가져올 페이지 번호(페이지 크기로 나누어진 페이지에 대한 번호)를 이용하여 가져올 시작 레코드 번호와 종료 레코드 번호를 계산하면 됩니다.

 

예를 들어, SELECT 된 결과(result-set)에 대한 전체 레코드 수가 14이고 페이지 크기가 5라면 전체 페이지 수는 3이 됩니다. 그리고 1 페이지를 가져온다면 페이지의 시작 레코드 번호는 1이 되고 페이지의 종료 레코드 번호는 5가 됩니다.

 

다른 예로, SELECT 된 결과(result-set)에 대한 전체 레코드 수가 100이고 페이지 크기가 20이라면 전체 페이지 수는 5가 됩니다. 그리고 2 페이지를 가져온다면 페이지의 레코드 시작 번호는 21이 되고 페이지의 레코드 종료 번호는 40이 됩니다.

 

그럼, 전체 레코드 수(recordTotalCount), 페이지 크기인 페이지 레코드 수(recordCountPerPage), 페이지 번호(pageNo)를 이용하여 쿼리에서 사용할 페이지의 레코드 시작 번호, 페이지의 레코드 종료 번호를 계산하겠습니다.

 

계산하기 전에 알아두어야 부분이 있습니다.

Oracle에서는 ROW_NUMBER를 사용하여 레코드의 번호가 부여되기 때문에 레코드의 번호가 1부터 시작되지만 MariaDB나 MySQL에서는 레코드의 번호가 0부터 시작됩니다.

 

그래서 Oracle은 1 베이스(one-based), MariaDB나 MySQL은 0 베이스(zero-based)로 계산되어야 합니다.

계산 공식이 1 베이스(Oracle)와 0 베이스(MariaDB, MySQL)에서도 계산되도록 시작 베이스와 종료 베이스를 설정해 줍니다. 우리는 MariaDB를 사용하고 있음으로 0 베이스로 계산됩니다.

int startBase = 1;
int endBase = 0;
if (zero) {
	startBase = 0;
	endBase = -1;
}

 

페이지의 레코드 시작 번호(pageStartRecordNo)는 페이지 번호에서 1을 빼고 페이지 레코드 수를 곱하고 시작 베이스를 더해주면 됩니다.

int pageStartRecordNo = ((pageNo - 1) * recordCountPerPage) + startBase;

 

페이지의 레코드 종료 번호(pageEndRecordNo)는 페이지 번호와 페이지 레코드 수를 곱하고 종료 베이스를 더해주면 됩니다.

int pageEndRecordNo = pageNo * recordCountPerPage + endBase;

페이지의 레코드 종료 번호가 전체 레코드 수보다 크면 전체 레코드 수로 변경해 줍니다.

if (pageEndRecordNo > (recordTotalCount + endBase)) {
	pageEndRecordNo = recordTotalCount + endBase;
}

 

페이지 마지막 번호(pageLastNo)는 전체 레코드 수를 페이지 레코드 수로 나누고 나머지가 있으면 1을 더해주면 됩니다.

int pageLastNo = (recordTotalCount / recordCountPerPage) + (recordTotalCount % recordCountPerPage == 0 ? 0 : 1);

 

 

Maven Spring Project에 Common Paging 추가하기

 

1. Java Resource > src/main/java에 com.home.study.common.search 패키지에 Paging 클래스를 생성합니다. (Paging.java)

  1. Paging 클래스에 Serializable가 구현(implements)되도록 class에 implements로 Serializable를 입력합니다.
  2. Paging 클래스 위로 마우스를 이동합니다. 콘텍스트 메뉴에서 "Add generated serial version ID"를 클릭하여 serialVersionUID를 생성합니다.
  3. 전체 레코드 수(recordTotalCount), 페이지 레코드 수(recordCountPerPage), 페이지 번호(pageNo), 페이지의 레코드 시작 번호(pageStartRecordNo), 페이지의 레코드 종료 번호(pageEndRecordNo) 변수를 생성하고 Getter, Setter를 생성합니다.
  4. 페이지 마지막 번호(pageLastNo) 변수를 생성하고 Getter만 생성합니다.
  5. Paging(페이징) 처리를 위해 계산 공식을 적용하여 process() 메서드를 생성합니다.
package com.home.study.common.search;

import java.io.Serializable;

public class Paging implements Serializable {

	/**
	 * serialVersionUID
	 */
	private static final long serialVersionUID = 5531728426097598480L;

	/**
	 * 전체 레코드 수
	 */
	private int recordTotalCount = 0;

	/**
	 * 페이지 레코드 수(기본값 : 10)
	 */
	private int recordCountPerPage = 10;

	/**
	 * 페이지 번호
	 */
	private int pageNo = 1;

	/**
	 * 페이지의 레코드 시작 번호
	 */
	private int pageStartRecordNo = 0;

	/**
	 * 페이지의 레코드 종료 번호
	 */
	private int pageEndRecordNo = 0;

	/**
	 * 페이지 마지막 번호
	 */
	private int pageLastNo = 0;

	/**
	 * 전체 레코드 수를 가져옵니다.
	 * @return 전체 레코드 수
	 */
	public int getRecordTotalCount() {
		return recordTotalCount;
	}

	/**
	 * 전체 레코드 수를 설정합니다.
	 * @param recordTotalCount 전체 레코드 수
	 */
	public void setRecordTotalCount(int recordTotalCount) {
		this.recordTotalCount = recordTotalCount;
	}

	/**
	 * 페이지 레코드 수를 가져옵니다.
	 * @return 페이지 레코드 수
	 */
	public int getRecordCountPerPage() {
		return recordCountPerPage;
	}

	/**
	 * 페이지 레코드 수를 설정합니다.
	 * @param recordCountPerPage 페이지 레코드 수
	 */
	public void setRecordCountPerPage(int recordCountPerPage) {
		if (recordCountPerPage <= 0) {
			recordCountPerPage = 10;
		}
		this.recordCountPerPage = recordCountPerPage;
	}

	/**
	 * 페이지 번호를 가져옵니다.
	 * @return 페이지 현재 번호
	 */
	public int getPageNo() {
		return pageNo;
	}

	/**
	 * 페이지 번호를 설정합니다.
	 * @param pageNo 페이지 현재 번호
	 */
	public void setPageNo(int pageNo) {
		if (pageNo <= 0) {
			pageNo = 1;
		}
		this.pageNo = pageNo;
	}

	/**
	 * 페이지의 레코드 시작 번호를 가져옵니다.
	 * @return 페이지의 레코드 시작 번호
	 */
	public int getPageStartRecordNo() {
		return pageStartRecordNo;
	}

	/**
	 * 페이지의 레코드 종료 번호를 가져옵니다.
	 * @return 페이지의 레코드 종료 번호
	 */
	public int getPageEndRecordNo() {
		return pageEndRecordNo;
	}

	/**
	 * 페이지 마지막 번호를 가져옵니다.
	 * @return 페이지 마지막 번호
	 */
	public int getPageLastNo() {
		return pageLastNo;
	}

	/**
	 * one 베이스(oracle)로 페이징을 처리합니다.
	 */
	public void processOne() {
		process(false);
	}

	/**
	 * zero 베이스(mariaDB, mySQL)로 페이징을 처리합니다.
	 */
	public void processZero() {
		process(true);
	}

	/**
	 * 페이징을 처리합니다.
	 * @param zero 제로 베이스 여부 (true : mariaDB, mySQL은 zero 베이스, false : oracle은 1 베이스)
	 */
	public void process(boolean zero) {
		// 레코드 번호
		int startBase = 1;
		int endBase = 0;
		// 제로 베이스 
		if (zero) {
			startBase = 0;
			endBase = -1;
		}
		
		// 페이지 마지막 번호 설정
		// 나머지가 있으면 페이지 마지막 번호를 증가 시킴
		pageLastNo = (recordTotalCount / recordCountPerPage) + (recordTotalCount % recordCountPerPage == 0 ? 0 : 1);
		if (pageLastNo > 0) {
			if (pageNo <= pageLastNo) {
				// 페이지의 레코드 시작 번호 설정
				pageStartRecordNo = ((pageNo - 1) * recordCountPerPage) + startBase;
				// 페이지의 레코드 종료 번호 설정
				pageEndRecordNo = pageNo * recordCountPerPage + endBase;
				// 페이지의 레코드 종료 번호가 전체 레코드 수보다 크면 전체 레코드 수로 변경
				if (pageEndRecordNo > (recordTotalCount + endBase)) {
					pageEndRecordNo = recordTotalCount + endBase;
				}
			} else {
				pageStartRecordNo = 0;
				pageEndRecordNo = 0;
			}
		} else {
			pageStartRecordNo = 0;
			pageEndRecordNo = 0;
			pageLastNo = 0;
		}
	}
}

 

 

 

Maven Spring Project의 Common Search Model에 Paging 추가하기

 

1. com.home.study.common.search 패키지에 있는 Search.java 파일을 오픈하고 추가합니다.

  1. Search 클래스에 Paging 클래스를 변수로 생성하고 Getter, Setter를 생성합니다.
/**
 * 페이징 객체
 */
private Paging paging = null;

/**
 * 페이징 객체를 가져옵니다.
 * @return 페이징 객체
 */
public Paging getPaging() {
	return paging;
}

/**
 * 페이징 객체를 설정합니다.
 * @param paging 페이징 객체
 */
public void setPaging(Paging paging) {
	this.paging = paging;
}

 

 

MariaDB, MySQL, Oracle의 Query(쿼리)에서 Paging 처리 방법

 

Oracle에서는 ROW_NUMBER를 사용하여 레코드의 번호를 부여하고 WHERE 절에서 페이지의 레코드 시작 번호와 페이지의 레코드 종료 번호를 사용하여 BETWEEN으로 레코드를 가져옵니다.

<select id="getList" parameterType="Search" resultMap="mapResult">
SELECT   *
       , RNUM
  FROM (
        SELECT   *
               , ROW_NUMBER() OVER(ORDER BY column_name) AS RNUM
          FROM table_name
          <include refid="whereSearch"/>
       )
 WHERE RNUM BETWEEN #{paging.pageStartRecordNo} AND #{paging.pageEndRecordNo}
 ORDER BY RNUM DESC
</select>

 

그러나 MariaDB나 MySQL에서는 페이지의 레코드 시작 번호부터 가져올 레코드 수만큼 레코드를 가져오기 때문에 레코드 종료 번호를 사용하지 않고 페이지 레코드 수를 사용합니다. MariaDB나 MySQL에서는 LIMIT으로 레코드를 가져옵니다.

<select id="getList" parameterType="Search" resultMap="mapResult">
SELECT *
  FROM table_name
 <include refid="whereSearch"/>
 LIMIT #{paging.pageStartRecordNo}, #{paging.recordCountPerPage}
</select>

 

 

Maven Spring Project의 Board Mapper XML에 Paging 추가하기

 

1. com.home.study.test1.sqlmapl.mappers.mariadb.board 패키지에 있는 BoardMapper.xml 파일을 오픈하여 Paging를 추가합니다.

  1. 우리는 MariaDB를 사용하고 있음으로 <select>에 LIMIT으로 페이지의 레코드 시작 번호와 페이지 레코드 수를 추가합니다.
<!--
	게시판 리스트를 가져옵니다.
-->
<select id="selectBoardList" parameterType="BoardSearch" resultMap="BoardMap">
	SELECT BRD.BRD_SEQ,
	       BRD.BRD_SUBJECT,
	       BRD.REG_ID,
	       DATE_FORMAT(BRD.REG_DTM, '%Y-%m-%d %H:%i:%s') AS REG_DTM
	  FROM INT_BOARD_TB BRD
	  <include refid="whereBoardSearch"/>
	  LIMIT #{paging.pageStartRecordNo}, #{paging.recordCountPerPage}
</select>

 

 

Maven Spring Project의 Service - Board Implement에 Paging 추가하기

 

1. Java Resource > src/main/java > com.home.study.test1.board.service.impl.BoardServiceImpl.java 파일을 오픈하여 Paging를 추가합니다.

  1. <form>에 paging이 없거나 boardSearch에서 paging를 생성하지 않을 경우 기본으로 처리되기 위해 paging를 생성합니다.
  2. paging에 검색 결과 수를 설정하고 페이징 처리를 위해 processZero() 메서드를 실행시킵니다. 우리는 MariaDB를 사용하고 있음으로 processZero()를 사용하였지만 Oracle를 사용하고 있다면 processOne() 메서드를 사용하면 됩니다.
package com.home.study.test1.board.service.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.home.study.common.search.Paging;
import com.home.study.test1.board.dao.IBoardDao;
import com.home.study.test1.board.model.Board;
import com.home.study.test1.board.model.BoardSearch;
import com.home.study.test1.board.service.IBoardService;

@Service
public class BoardServiceImpl implements IBoardService {

	@Autowired
	IBoardDao boardDao;

	@Override
	public List<Board> selectBoardList(BoardSearch boardSearch) {
		if (boardSearch.getPaging() == null) {
			boardSearch.setPaging(new Paging());
		}
		
		int count = boardDao.selectBoardListCount(boardSearch);
		boardSearch.getPaging().setRecordTotalCount(count);
		
		List<Board> boardList = null;
		if (count > 0) {
			boardSearch.getPaging().processZero();
			boardList = boardDao.selectBoardList(boardSearch);
		}
		return boardList;
	}
}

 

 

Maven Spring Project의 main.jsp에 Paging UI 추가하기

 

1. /src/main/webapp/WEB-INF/views/main/main.jsp 파일을 오픈하여 <form> 태그 안에 paging UI를 추가합니다.

  1. 검색 결과 영역을 <div> 태그로 생성합니다.
  2. 검색 결과 영역에 전체 검색 결과 수를 보여주기 위해 boardSearch 객체에서 paging 객체의 recordTotalCount 변수를 가져옵니다.
  3. 검색 결과 영역에 페이지 크기를 변경할 수 있게 <form:select> 태그를 사용하여 path 속성에 paging 객체의 recordCountPerPage 변수를 지정합니다.
<form:form modelAttribute="boardSearch" autocomplete="off">
	<div>
		<form:select path="searchType">
			<form:option value="subject" label="제목" />
			<form:option value="subjectandcontent" label="제목+내용" />
		</form:select>
		<form:select path="searchCondition">
			<form:option value="equal" label="일치" />
			<form:option value="like" label="포함" />
		</form:select>
		<form:input path="searchKeyword" placeholder="검색할 키워드를 입력하세요." />
		<button id="searchBtn" type="button">검색</button>
	</div>
	<div class="search-result">
		<div>
			검색 결과 : ${boardSearch.paging.recordTotalCount}건
		</div>
		<div>
			<form:select path="paging.recordCountPerPage">
				<form:option value="5" label="5" />
				<form:option value="10" label="10" />
				<form:option value="25" label="25" />
				<form:option value="50" label="50" />
				<form:option value="100" label="100" />
			</form:select>
		</div>
	</div>
</form:form>

 

 

2. <style> 태그에 검색 결과 영역에 대한 CSS를 추가합니다.

.board form .search-result { display: flex; justify-content: space-between; margin-top: 10px; }

 

3. script를 추가합니다.

  1. 검색 결과 영역에서 페이지 크기를 변경하면 form를 submit 하게 합니다.
$('select[name="paging.recordCountPerPage"]').change(function(event) {
	boardSearch.submit();
});

 

전체 소스입니다.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>게시판</title>
		<style>
			.board { width:800px; margin: 20px auto; }
			.board table { width:100%; border-top:2px solid #1d4281; border-spacing:0; }
			.board table thead th { padding:8px 10px 10px 10px; vertical-align:middle; color:#1d4281; font-size:14px; border-bottom:1px solid #ccc; background:#f8f8f8; }
			.board table tbody td { padding:7px 10px 9px 10px; text-align:center; vertical-align:middle; border-bottom:1px solid #ccc; font-size:14px; line-heighT:150%; }
			.board table tbody td:nth-child(2) { text-align:left;}
			.board form { margin-bottom: 10px; }
			.board form select { padding: 4px 4px; }
			.board form input[type="text"] { padding: 4px 4px; width: 200px; }
			.board form button { display: inline-flex; height: 27px; font-size: 14px; }
			.board form .search-result { display: flex; justify-content: space-between; margin-top: 10px; }
		</style>
	</head>
	<body>
		<div class="board">
			<form:form modelAttribute="boardSearch" autocomplete="off">
				<div>
					<form:select path="searchType">
						<form:option value="subject" label="제목" />
						<form:option value="subjectandcontent" label="제목+내용" />
					</form:select>
					<form:select path="searchCondition">
						<form:option value="equal" label="일치" />
						<form:option value="like" label="포함" />
					</form:select>
					<form:input path="searchKeyword" placeholder="검색할 키워드를 입력하세요." />
					<button id="searchBtn" type="button">검색</button>
				</div>
				<div class="search-result">
					<div>
						검색 결과 : ${boardSearch.paging.recordTotalCount}건
					</div>
					<div>
						<form:select path="paging.recordCountPerPage">
							<form:option value="5" label="5" />
							<form:option value="10" label="10" />
							<form:option value="25" label="25" />
							<form:option value="50" label="50" />
							<form:option value="100" label="100" />
						</form:select>
					</div>
				</div>
			</form:form>
			<table>
				<colgroup>
					<col style="width:10%">
					<col style="width:*">
					<col style="width:15%">
					<col style="width:25%">
				</colgroup>
				<thead>
					<tr>
						<th scope="col">번호</th>
						<th scope="col">제목</th>
						<th scope="col">작성자</th>
						<th scope="col">작성일</th>
					</tr>
				</thead>
				<tbody>
					<c:forEach var="boardItem" items="${boardList}" varStatus="status">
					<tr>
						<td>${fn:length(boardList) - status.count + 1}</td>
						<td>${boardItem.subject}</td>
						<td>${boardItem.registrationId}</td>
						<td>${boardItem.registrationDateTime}</td>
					</tr>
					</c:forEach>
				</tbody>
			</table>
		</div>
		<script type="text/javascript">
			$(function() {
				var boardSearch = $('#boardSearch');
				$('#searchBtn').click(function() {
					$(this).attr("disabled", "disabled");
					boardSearch.submit();
				});
				$('#searchKeyword').keypress(function(event){
					if (13 == event.which) {
						$('#searchBtn').click();
						return false;
					}
				});
				$('select[name="paging.recordCountPerPage"]').change(function(event) {
					boardSearch.submit();
				});
			});
		</script>
	</body>
</html>

 

 

Maven Spring Project를 실행하여 웹 브라우저로 확인하기

 

1. "Servers"탭에서 "Tomcat9"를 선택하고 "start"버튼(start the server)을 클릭하면 Tomcat이 실행됩니다.

 

2. 웹 브라우저에서 "http://localhost:9000/index.do"를 입력합니다. 기본적으로 페이지 크기는 10입니다.

 

3. 페이지 크기를 10에서 5로 변경합니다. 그러면 페이지 크기가 5로 계산되어 5개만 가져오는 것을 확인할 수 있습니다.

 

728x90
반응형