본문 바로가기
Framework/Spring

[SPRING 시작-11] #게시판 페이징 처리하기 / board paging

by 나비와꽃기린 2016. 10. 25.




기존에 paging 처리를 설명했던 포스팅도 있습니다

게시판 페이징 서버단 >> http://mkil.tistory.com/227

게시판 페이지 클라이언트단 >> http://mkil.tistory.com/228

위 포스팅은 나눠 설명이 되어져 있기 때문에 이해도가 조금 떨어질 수도 있어요.

또한, 같은 내용이긴 하지만 위 예제들은 필자가 따로 사용했던 EppltPortlet

DataEntitiy등 별도의 Class 및 용어들이 보이기 때문에 아마 다른 분들이 가져다가

사용하시기에는 번거로울..것 같다는 ^^; 생각이 들었습니다.

 

따라서 이 소스를 기본바탕으로 해서 기본적인 PagingUtil 예제를 작성해보도록 할게요..

 

이전 포스팅을 참조하여 환경 및 예제가 어디까지 진행 되었는지 확인해 주세요

게시판 목록 만들기 >> http://mkil.tistory.com/310

게시물 쓰기 >> http://mkil.tistory.com/313

게시물 상세보기 >> http://mkil.tistory.com/326



(1) util 패키지 생성



paging 처리를 위한 com.common.utils 패키지와 하위 PagingUtil CLASS를 생성합니다.





 

(2) PagingUtil 클래스 작성



생성한 PagingUtil.java를 다음과 같이 작성합니다.

자세한 설명은 http://mkil.tistory.com/227 에 해놨기 때문에 생략할께요.


package com.common.utils;

import java.util.Map;

public class PagingUtil {

	private static final int countPerPage = 10;
	private static final int unitPage = 10;

	/** 페이지 정보 셋팅 **/
	public static Map setPageInfo(Map reqParam, int defaultCountPerPage) {
		int pageNo = Integer.parseInt( reqParam.get("pageNo").toString() );  //java Object -> int 형 변환
		int countPerPage = Integer.parseInt( reqParam.get("countPerPage").toString() );

		countPerPage = countPerPage < 100 ? countPerPage : 100; // 최대 100개로 제한

		int first = ((pageNo - 1) * countPerPage) + 1, last = first + countPerPage - 1;

		reqParam.put("countPerPage", countPerPage);
		reqParam.put("first", first);
		reqParam.put("last", last);
		
		System.out.println("setPageInfo  :::: "+reqParam);
		
		return reqParam;
	}
	
	/** 페이징 처리 (파라미터 2개) **/
	public static Map getPageObject(int totalCount, int currentPageNo)
	{
		return getPageObject(totalCount, currentPageNo, 10);
	}
	
	/** 페이징 처리 (파라미터 3개) **/
	public static Map getPageObject(int totalCount, int currentPageNo, int countPerPage)
	{
		return getPageObject(totalCount, currentPageNo, countPerPage, 10);
	}
	
	/** 페이징 처리 (파라미터 4개) **/
	public static Map getPageObject(int totalCount, int currentPageNo, int countPerPage, int unitPage) {
		int currPage = currentPageNo;
		int unitCount = 100;

		boolean isFirst = false;

		if (totalCount == 0) {
			countPerPage = unitCount;
		} else if (totalCount < countPerPage) {
			countPerPage = totalCount / unitCount * unitCount;
			if (totalCount % unitCount > 0) {
				countPerPage += unitCount;
			}
		}

		int totalPage = getMaxNum(totalCount, countPerPage);

		if (totalPage < currPage)
			currPage = totalPage;
		
		int currStartCount;
		int currEndCount;

		if (currPage != 1) {
			currEndCount = currPage * countPerPage;
			currStartCount = currEndCount - countPerPage;
		} else {
			currEndCount = countPerPage;
			currStartCount = 0;
		}

		if (currEndCount > totalCount)
			currEndCount = totalCount;
		
		int currStartPage;
		int currEndPage;

		if (totalPage <= unitPage) {
			currEndPage = totalPage;
			currStartPage = 1;
		} else {
			currEndPage = (currPage - 1) / unitPage * unitPage + unitPage;
			currStartPage = currEndPage - unitPage + 1;
		}

		if (currEndPage > totalPage)
			currEndPage = totalPage;

		int prePage;
		boolean prePage_is;
		if (currStartPage != 1) {
			prePage_is = true;
			prePage = currStartPage - 1;

		} else {
			prePage_is = false;
			prePage = 0;
		}
		int nextPage;
		boolean nextPage_is;

		if (currEndPage != totalPage) {
			nextPage_is = true;
			nextPage = currEndPage + 1;
		} else {
			nextPage_is = false;
			nextPage = 0;
		}

		Map tempJSON = new java.util.HashMap();
		try {
			tempJSON.put("currPage", Integer.valueOf(currPage));
			tempJSON.put("unitPage", Integer.valueOf(unitPage));
			tempJSON.put("prePage", Integer.valueOf(prePage));
			tempJSON.put("prePage_is", Boolean.valueOf(prePage_is));
			tempJSON.put("nextPage", Integer.valueOf(nextPage));
			tempJSON.put("nextPage_is", Boolean.valueOf(nextPage_is));
			tempJSON.put("currStartPage", Integer.valueOf(currStartPage));
			tempJSON.put("currEndPage", Integer.valueOf(currEndPage));

			tempJSON.put("totalCount", Integer.valueOf(totalCount));
			tempJSON.put("totalPage", Integer.valueOf(totalPage));
		} catch (Exception localException) {
		}

		return tempJSON;
	}

	/** max 페이지 구하기 **/
	private static int getMaxNum(int allPage, int list_num) {
		if (allPage % list_num == 0) {
			return allPage / list_num;
		}
		return allPage / list_num + 1;
	}

}


(3) Service 수정



데이터 List를 가져오던 Service부분을 수정합니다.

기존에는 service에서 data만 가지고 왔죠?

지금은 Data를 가져올 Table의 총 개수를 가지고 온 뒤, 총 개수를 가지고

한 페이지당 출력하고 싶은 unit 개수를 사용하여

몇 개씩 data를 가지고 올 것인지 정하는 로직이 추가 되었습니다.

즉 쉽게 말하자면 데이터 총 갯수를 구해 와서 페이지의 시작값과 끝값을 계산해주는 것입니다.

 

그게 바로 setPageInfo 라는 거구요.

저는 한 페이지당 10개의 data를 가지고 오는 것으로 설정해 주었답니다.

setPageInfo에 대한 자세한 설명 여기에 다 있습니다.>> http://mkil.tistory.com/227



public Map selectBoardList(Map param) {

	Map resultObject = new HashMap();
	List result = new ArrayList();
		
	int totalCnt = boardDao.selectListCnt(param); // DB연동_ 총 갯수 구해오기
	
	int searchNo =  10;
	int searchCntPerPage =  10;
	int searchUnitPage = 10;
	
	if (totalCnt > 0) {

		PagingUtil.setPageInfo(param, 10);  //param에 Page정보 파라미터 정보 put 해주기
		result = boardDao.selectList(param); // 게시판 목록 data 페이징 처리 갯수만큼 가져오기

		//dataList와 pageInfo 셋팅 해주고 return 하기
		resultObject.put("result", result);
		resultObject.put("page", PagingUtil.getPageObject(totalCnt, searchNo, searchCntPerPage, searchUnitPage ));

	} else {
		
		resultObject.put("result", result); // 빈값
		resultObject.put("page", PagingUtil.getPageObject(totalCnt, 0));
	}
	
	return resultObject;

}


(4) selectListCntdao xml 생성



service에서 새로 생성한 selectListCnt 메소드의 dao를 다음과 같이 만들고,


public int selectListCnt(Map param) {
	int result = sqlsession.selectOne("boardSql.selectListCnt",param);
	return  result ;
}

xml count(*)를 사용하여 총 개수를 구해 옵니다.

<select id="boardSql.selectListCnt" resultType="Integer">
	 SELECT count(*) FROM BOARD_MAIN_TEST
</select>

(5) selectList 쿼리 수정



기존의 selectList 쿼리를 다음과 같이 수정합니다.


<resultMap id="getBoardListResult" type="HashMap">
	<result property="BRD_TYPE" column="BRD_TYPE" />
	<result property="BRD_CD" column="BRD_CD" />
	<result property="DOCNUM" column="DOCNUM" />
	<result property="ADD_USR_NM" column="ADD_USR_NM" />
	<result property="TITLE" column="TITLE" />
	<result property="CONTENTS" column="CONTENTS" jdbcType="CLOB" javaType="java.lang.String" />
	<result property="ATTACH" column="ATTACH" />
	<result property="DEL_CONF" column="DEL_CONF" />
	<result property="VIEWCOUNT" column="VIEWCOUNT" />
</resultMap>


 <select id="boardSql.selectList" parameterType="java.util.HashMap" resultMap="getBoardListResult">
	SELECT * FROM
		(SELECT  ROW_NUMBER() OVER(ORDER BY DOCNUM DESC) RNUM,
				BRD_TYPE,
				BRD_CD,
				DOCNUM,
				ADD_USR_NM,
				TITLE,
				CONTENTS,
				ATTACH,
				DEL_CONF,
				VIEWCOUNT
		 FROM BOARD_MAIN_TEST
		) X WHERE X.RNUM BETWEEN #{first} AND #{last}
</select>  


사실, 이 부분에서 내부 서버 오류가 나서 한참 뻘짓했습니다 (;;)

resultMap을 사용안하고 resultMap에 그냥 HashMap으로 지정해서 했을때 

분명 Controller 단까지 에러없이 result data를 잘 가져오는데 

화면에서 ajax 500 에러가 나더군요..........


찾아보니 CONTENTS 타입을 가져오면서 서버 내부에서 오류를 뿜는것을 확인했습니다. (에러 로그가 빨갛게 떨어지지 않아서 한참 찾았네요 까막눈...인가 @__@끙)

이유는,, Mybatis를 사용하면서 DB 데이터 타입에 CLOB, BLOB등의 타입을 selec할때는 ResultMap을 지정해줘야 했기 때문이었습니다.


예제 작성하다가 하나 배워가네요 ^^;



(6) JSON 핸들링을 위한 디펜던시 추가



 jsp에서 ajax 사용을 하여 JSON을 핸들링 하기 위해 다음과 같인 디펜던시를

 추가해 줍니다. jackson 라이브러리는 어떠한 형태의 데이터도 json 형태의 데이터로 자동변환을 해준답니다.


<dependency>
	<groupId>org.codehaus.jackson</groupId>
	<artifactId>jackson-mapper-asl</artifactId>
	<version>1.9.13</version>
</dependency>

 

Controller도 수정해야 하는데, 일단 JSP단을 먼저 수정하고 나서

Controller가 수정되어야 조금은 이해하기 수월할 것 같다고 생각이 들어서

jsp부터 수정하도록 하겠습니다.


(7) ajax를 사용하여 Data 호출



Ajax에 대한 설명 및 예제는 여기를 한번 읽고 오시면 이해하기 더 쉬울수도 ^^

>> http://mkil.tistory.com/90 (아주 기초적인 부분만 설명이 되어있네요;;)

 

아무래도 페이징을 위해서는 화면단 부분의 작성이 중요하겠죠?

ajax는 비동기적 통신을 위한 데이터 전송 방법이기 때문에 클라이언트단과 서버단이 내부적으로 통신을 한답니다.

따라서 클라이언트의 요청에 따라 서버는 데이터를 화면의 재 로딩없이 전달해 줄 수 있죠.

기존에 작성했던 boardMain.jsp를 다음과 같이 수정합니다.



<SCRIPT LANGUAGE="JavaScript">

$(document).ready(function(){
	boardMain.init();
});

var boardMain = {
		
		init : function(){
			
			var _this = this;
			_this.btnEvent();
			_this.getBoardList();
			
		}
		,btnEvent : function(){
			
			/* 게시글 제목 클릭 상세보기 */
			$('.boardTitle').on('click',function(){
				var popUrl = "/spring/boardDetail?docnum="+$(this).attr('id');	//팝업창에 출력될 페이지 URL
				var popOption = "width=570, height=360, resizable=no, scrollbars=no, status=no;";    //팝업창 옵션(optoin)
				window.open(popUrl,"",popOption);

			});
			
			/* 작성하기 클릭  */
			$('input[type=button]').on('click',function(){
				var url = "/spring/boardWrite";//팝업창에 출력될 페이지 URL
				location.href = url;
			});
			
		}
		// ************ 3번영역  ***************** 
		,getBoardList : function(no){

			var pageNo = (no || 1);
			
			$.ajax({
				type:"GET",
				url: '/spring/boardList',
				dataType: "json",
				data : "countPerPage="+10+"&pageNo="+pageNo,
				contentType:"application/json; charset=UTF-8",
				cache 	: false,
				success : function(resData){
					
					var item=resData.result;
					var selectHtml=[];
					var len=item.length;
					
					var page=resData.page; //페이징 변수
					var page_boardList = Paging(page.totalCount, 10, 10 ,pageNo, "boardList"); //공통 페이징 처리 함수 호출
				
					//데이타 그리기
					if(len >0){
						$(item).each(function(i, item){
	 						
							selectHtml.push('<tr>');
							selectHtml.push('<th ><a href="#" >'+item.DOCNUM+'</a></th>');
							selectHtml.push('<th class="boardTitle" id="'+item.DOCNUM+'"><a href="#">'+(item.TITLE || "제목없음")+'</a></th>');
							selectHtml.push('<td>'+item.ADD_USR_NM+'</td>');
							selectHtml.push('<td>'+item.VIEWCOUNT+'</td>');
							selectHtml.push('</tr>');
						});
					}else{
						selectHtml.push('<tr>');
						selectHtml.push('<td colspan="3">조회된 결과가 없습니다.</td>');
						selectHtml.push('</tr>');
					}
					
					$("#boardList").empty().html(selectHtml.join(''));
					
					//페이징 그리기
					$("#paging").empty().html(page_boardList);
					
				},
				/* ajax error 확인방법 */
				error 	: function(request,status,error){
				    console.log(request);
				    console.log(status);
				    console.log(error);
				}
			});
			
			
		}
		
		
}

//************ 4번영역  ***************** 
var goPaging_boardList = function(cPage){
	boardMain.getBoardList(cPage); // boardAdmin 개체의 getBoardList 함수를 다시 호출
};	


// ************ 2번영역  ***************** --> 목록 페이징 함수
Paging = function(totalCnt, dataSize, pageSize, pageNo, token){
           totalCnt = parseInt(totalCnt);	// 전체레코드수
           dataSize = parseInt(dataSize);   // 페이지당 보여줄 데이타수
           pageSize = parseInt(pageSize);   // 페이지 그룹 범위       1 2 3 5 6 7 8 9 10
           pageNo = parseInt(pageNo);       // 현재페이지
          
           var  html = new Array();
           if(totalCnt == 0){
                      return "";
           }
          
           // 페이지 카운트
           var pageCnt = totalCnt % dataSize;         
           if(pageCnt == 0){
                      pageCnt = parseInt(totalCnt / dataSize);
           }else{
                      pageCnt = parseInt(totalCnt / dataSize) + 1;
           }
          
           var pRCnt = parseInt(pageNo / pageSize);
           if(pageNo % pageSize == 0){
                      pRCnt = parseInt(pageNo / pageSize) - 1;
           }
          
           //이전 화살표
           if(pageNo > pageSize){
                      var s2;
                      if(pageNo % pageSize == 0){
                                  s2 = pageNo - pageSize;
                      }else{
                                  s2 = pageNo - pageNo % pageSize;
                      }
                      html.push('<a href=javascript:goPaging_' + token + '("');
                      html.push(s2);
                      html.push('");>');
                      html.push('◀ ');
                      html.push("</a>");
           }else{
                      html.push('<a href="#">\n');
                      html.push('◀ ');
                      html.push('</a>');
           }
          
           //paging Bar
           for(var index=pRCnt * pageSize + 1;index<(pRCnt + 1)*pageSize + 1;index++){
                      if(index == pageNo){
                                  html.push('<strong>');
                                  html.push(index);
                                  html.push('</strong>');
                      }else{
                                  html.push('<a href=javascript:goPaging_' + token + '("');
                                  html.push(index);
                                  html.push('");>');
                                  html.push(index);
                                  html.push('</a>');
                      }
                      if(index == pageCnt){
                                  break;
                      }else html.push('  |  ');
           }
            
           //다음 화살표
           if(pageCnt > (pRCnt + 1) * pageSize){
                      html.push('<a href=javascript:goPaging_' + token + '("');
                      html.push((pRCnt + 1)*pageSize+1);
                      html.push('");>');
                      html.push(' ▶');
                      html.push('</a>');
           }else{
                      html.push('<a href="#">');
                      html.push(' ▶');
                      html.push('</a>');
           }
 
           return html.join("");
}


</script>
</head>
<body>

	<table class="tbl_port" style="border: 1px solid #ccc">
		<caption style="background-color: #ccc">목록</caption>
		<colgroup>
			<col width="10%" />
			<col width="*"/>
			<col width="15%"/>
			<col width="10%"/>
		</colgroup>
		<thead>
			<tr>
				<th scope="col">글번호</th>
				<th scope="col">제목</th>
				<th scope="col">작성자</th>
				<th scope="col">조회수</th>
			</tr>
		</thead>
		<!-- ************* 1번영역  ***************** -->
		<tbody id="boardList">
			
		</tbody>
	</table>
	<br/>
	<div id="paging" style="margin-left: 190px;"></div>
	<br/>
	<div ><input type="button" value="작성하기 " style="margin-left: 440px;"/></div>

</body>
</html>



제가 1번영역 부터 4번영역까지 주석을 달아놨어요.

각 영역별로 설명 하겠습니다.

 

①번 영역 설명

dataListpagingInfo를 같이 가져와 출력해 줄 것이기 때문에

기존의 JSTL을 사용하던 목록을 지우고 baordList 라는 id값을 추가하고

id값이 paging div를 추가해 주었습니다.

 

번 영역 설명

Paging 이라는 page UI를 그려주는 공통 자바스크립트 함수를 작성합니다.

이 부분은 공통 js로 따로 관리해 주세요.

지금은 테스트 예제니까 같은 JSP 안에 두겠습니다 (;;)

 

번 영역 설명

getBoardList라는 함수에서는 클릭한 pageNo 를 가지고 ajax를 통해

countPerPage(한 페이지당 가져올 데이터 개수) 수만큼 데이터를 가지고 옵니다.

pageNo1,2,3 페이지에 따라 10~20, 21~30, 31~40 이렇게 계산되어져 오는거죠.

 

가져온 dataList를 가지고 동적으로 table을 그려줍니다.

pagingPaging 공통함수에 서버를 통해 가져온 page 값을 파라미터 변수로 넘겨

그려줍니다.

Paging에서 return html 객체를 통으로 id값이 paging인 영역에 그려주는 것이죠.

 

번 영역 설명

Paging 함수 호출 시, 마지막 파라미터값에 따라 “goPaging_”+”파라미터값의 함수를

호출하도록 만들었습니다.

따라서 우리는 goPaging_boardList 함수를 별도로 만들어 줘야합니다.

이 함수는 page번호를 클릭할때마다 클릭한 페이지 값을 ajax를 호출하는 함수로 넘겨주는 역할을 해줍니다.

boardMain.getBoardList 는 제가 가장 상단에 임의로 정한 객체와 함수 명이기 때문에

바꾸셔서 사용하셔도 되겠죠?


(7) 결과 확인

다음과 같이 페이징 된 게시판이 나오게 됩니다.



자세히 작성해보자 하면서 했는데 오히려두서없이 적은 것 같네요

이상한 부분이나, 오타, 에러를 발견하시면 댓글 달아주세요~

 

여기까지 jQuery를 사용한 게시판 Paging 이였습니다. ^^

댓글2

  • 음... 2017.05.29 16:59

    컨트롤 수정은 어디에 있나요 ?
    답글

  • 컨트롤 수정이 뭘 말씀하시는지 모르겠지만...

    게시글 목록을 가져오는 컨트롤러를 말씀하시는 거라면, 이 포스팅의 (7)ajax에서 데이타 호출 에서
    '/spring/boardList' url로 데이터 목록을 가져오는 부분의 컨트롤러는 따로 만드셔야 합니다.

    컨트롤러를 만드는것은 http://mkil.tistory.com/310 포스팅의
    /boardMain 처럼 만들어주셔야 하겠네요~
    해당 url포스팅 참조해주세요
    답글