본문 바로가기
Spring

대용량 페이징 기법

by 아이티.파머 2014. 8. 7.
반응형


많은 양의 데이터를 한 곳에서 읽어 온다고 하면 db에 부하가 가기 마련이다.
이때  대용량 페이징 기법을 사용 하면 유용 할 것이다.


대부분의 사람들이 알고 있듯이, 페이징 시에 사용되는 토탈 갯수를 세지 않고
페이징 하고 싶은 총 갯수의 범위를 두고 작업을 하면 되겠다.



한화면의 페이징 범위 : 10
보여줄 목록 갯수 : 10
검색할 총 갯수 : 100

* 이전, 다음 표현 방법

이와 같이 한다면 다음과 이전을 택할때 101 개를 기준으로 검색을 하고 100개를 넘어간 101 갯수를 보유 시 "다음 " 페이지를 보여준다. 
"이전" 페이지는 페이지 리스트 처음 페이지 번호가  한페이지에 보여지는 페이지 건수 보다 큰경우에만 보여 주도록 한다.


* 선택된 페이지만 보여줄 방법

 firsDatatNo , lastDataNo  값 셋팅을 위해 페이지검색 타입에 따라 데이터를 정렬 시켜 준다.
다음 페이지 검색시 문제가 되지 않으나, 이전 페이지 선택시  firsDatatNo , lastDataNo  거꾸로 나올 수있기 때문이다.



JSP file

function fnPageLink(pageNo , pagingType) {
            $( '#pageIndex').val(pageNo);
            $( '#pagingType').val(pagingType);
            
             if(pagingType == 'PAGING_AFTER' ) {
                  $( '#FIRST_DATA_NO').val($( '#LAST_DATA_NO').val());
            } else if((pagingType == 'PAGING_BEFOR' )){
                  $( '#FIRST_DATA_NO').val(eval($( '#FIRST_DATA_NO').val()-1));
            }          
            
            $( '#searchForm').attr( 'action', '').submit();
}

...
< input type= "text" name = "FIRST_DATA_NO" id= "FIRST_DATA_NO" value= " <%= pagingList.getPaginationInfo().getFirstDataNo()%> " >
<input type = "text" name= "LAST_DATA_NO" id= "LAST_DATA_NO" value= " <%= pagingList.getPaginationInfo().getLastDataNo() %> ">
<input type = "text" name= "pagingType" id= "pagingType" value = "">
                                                                        
<input type = "hidden" name= "pageIndex" id= "pageIndex" value= "${pageIndex} " >
<input type = "hidden" name= "projectNo" id= "projectNo" value = "<%= sfProject.getProjectNo()%> ">
....


Controller  file

Map<String , Object>  searchOption = new HashMap<>();
int pageIndex =  ServletRequestUtils.getIntParameter (request, "pageIndex", 1);
long projectNo = NumberUtils. toLong( request.getParameter( "projectNo" ), -1 );

searchOption.put("pagingType", ServletRequestUtils.getStringParameter(request, "pagingType" ));
searchOption.put( "FIRST_DATA_NO" ServletRequestUtils.getStringParameter(request, "FIRST_DATA_NO" ));


또한
    
 
          // 데이터 정렬 
          if ( StringUtils.defaultString((String)dataMap.get( "pagingType"), "")  .equals("PAGING_BEFOR" )) {
                  Collections. reverse( list );
          }
            
             if(list.size() > 0) {
                  Map<String, Object> aa= (Map<String, Object>)list.get(0);
                   try {
                        String firstDataNo = String.valueOf((Long)aa.get( "no"));
                         paginationInfo.setFirstDataNo(Integer. parseInt(firstDataNo));
                  } catch (Exception e) {
                        
                  }
            }
            
             if(list.size() > 100) {
                  Map<String, Object> aa= (Map<String, Object>)list.get(list.size() -1);
                   try {
                        String lastDataNo = String.valueOf((Long)aa.get( "no"));
                         paginationInfo.setLastDataNo(Integer. parseInt(lastDataNo));
                  } catch (Exception e) {
                        
                  }
            }

            pagingList.setPaginationInfo (paginationInfo);
            List<Object> pageDataList = new ArrayList<>();
            
            // 화면에 보여줄 뷰잉 데이터만 따로 분리한다.
             int offSet = ( currentPageNo - (paginationInfo.getFirstPageNoOnPageList()) ) * pageSize;
             forint i = 0; i < 10 && ( offSet + i) < list.size() ; i++ ) {
                  pageDataList.add( list.get( offSet + i ) );
            }
            
            pagingList.setSelectList(list);                    // 검색된 전체 데이터
            pagingList.setPageDataList(pageDataList);          // 화면에 보여줄 데이터





* 필요한 변수

private int pageItemCount ;          // 한페이지에 보여줄 아이템 갯수
private int pageSize;                // 한페이지에 보여지는 페이지 건수
private int currentPageNo;           // 선택한 페이지 번호
      
private int firstDataNo;             // 첫번째 데이터 번호값
private int lastDataNo;                    // 마지막 데이터 번호값
         
private int firstPageNoOnPageList;   // 페이지 리스트 처음 페이지 번호
private int lastPageNoOnPageList;    // 페이지 리스트 마지막 페이지 번호

private int firstRecordIndex;        // 페이지 검색 시작점


* 페이지 시작점 = (선택 번호-1) * (한페이지에 보여줄 아이템 갯수)
                    (1 - 1 ) * 10  = 0
                    (2 - 1 ) * 10  = 10
                         
                    limit 0  offset 10
                    limit 10 offset 10


* 처음 페이지 = ((선택 번호-1) * (한페이지에 보여줄 아이템 갯수)) * (한페이지에 보여줄 아이템 갯수) +1
                (((1 - 1 ) * 10) / 10 )+1  = 1
                (((2 - 1 ) * 10) / 10 )+1  = 1
                              .
                              .     
                              .
                (((11 - 1 ) * 10) / 10 )+1  = 11

* Parameter 로 pageType 과 FIRST_DATA_NO 를 받아 사용한다.






* 이전 과 다음 Query 설정
     - 다음 페이지 와 페이지 선택시는 "<=" 기준보다 작거나 같은것으로 검색, 정렬은 역순 DESC
          ex ) <= 1500  , DESC
     - 이전 페이지는 ">" 기준보다 큰것으로 검색 하며, 정렬은 정순 ASC
          ex ) > 1500 , ASC

               < choose>
                    <when test = "pagingType != 'PAGING_BEFOR' and pagingType != null">
                         <if test = "FIRST_DATA_NO != null and FIRST_DATA_NO != 1">
                              AND no  <![CDATA[<= ]]> #{FIRST_DATA_NO}
                         </if >
                    </when >
                    <when test = "pagingType == 'PAGING_BEFOR' and pagingType != null">
                         AND no  <![CDATA[ > ]]> #{FIRST_DATA_NO}
                    </when >
                    <otherwise ></otherwise >
               </choose >

               < choose>
                   <when test = "pagingType != 'PAGING_BEFOR' and pagingType != null">
                         <if test = "FIRST_DATA_NO != null and FIRST_DATA_NO != 1">
                              h.no desc ,
                         </if >
                   </when >
                   <when test = "pagingType == 'PAGING_BEFOR' and pagingType != null">
                        h.no asc ,    
                   </when >
                   <otherwise ></otherwise >
               </choose >
               Limit 101
             



* 페이징 렌더러  (페이징 표시를 화면에 보여주기 위한 작업)

     - 화면에 보여줄 페이지 계산 = (전체 검색된 아이템 갯수 -1 / 페이지 사이즈)+1

import java.text.MessageFormat;


/**
 * previousPageLabel [이전]
 * nextPageLabel [다음]
 * @author ask
 *
 */
public class PaginationRenderer {
      
       public String previousPageLabel = "<li><a href=\"#\" class=\"prev\" onclick=\"{0}({1},{2}); return false;\">이전</a></li>" ;
      
       public String currentPageLabel = "<li class=\"active\"><a href=\"javascript:;\">{0}</a></li>";
       public String otherPageLabel = "<li><a href=\"#\" onclick=\"{0}({1}); return false;\">{2}</a></li>";
      
       public String nextPageLabel = "<li><a href=\"#\" class=\"next\" onclick=\"{0}({1},{2}); return false;\">다음</a></li>" ;
      
       public String renderPagination(PaginationInfo paginationInfo,PagingList pagingList , String jsFunction) {
            
             int currentPageNo = paginationInfo.getCurrentPageNo();                   // 선택한 페이지 번호
             int selectListSize   =  pagingList.getSelectList().size();               // 검색된  페이지의  전체 아이템 갯수
            
             int firstPageNoOnPageList = paginationInfo.getFirstPageNoOnPageList();   // 현재 페이지의 시작점  EX:) 1/11/21
             int pageViewingNo = (( selectListSize -1 ) / 10 ) + 1;          // 화면에 보여줄 페이지 계산(1~10) 몇개     ((전체 사이즈 -1 / 기준 페이지 사이즈 ))+1
            

            StringBuffer sb = new StringBuffer();
            sb.append( "<ul class=\"pagination\">\n" );

            // 페이지의 시작점 즉 첫 번호가  10보다 큰경우만 이전 표시
             if(firstPageNoOnPageList > 10) {
                  sb.append(MessageFormat. format( previousPageLabelnew Object[] { jsFunction, Integer.toString(firstPageNoOnPageList - paginationInfo.getPageSize()) , "\'PAGING_BEFOR\'" })); // 이전  (첫번째 페이지 번호 - 페이지 사이즈)
            }
            
                   //검색된 사이즈 양 만큼 번호 표시, 기본 10 그리고 pageViewingNo 의 조건
                   for ( int i = 0 ; i < 10 && i < pageViewingNo ; i++) {
                         if (i+(firstPageNoOnPageList) == currentPageNo) {
                              sb.append(MessageFormat. format( currentPageLabelnew Object[] { Integer.toString((i) + firstPageNoOnPageList) }));
                        }
                         else {
                              sb.append(MessageFormat. format( otherPageLabelnew Object[] { jsFunction, Integer.toString((i) + firstPageNoOnPageList), Integer.toString((i) + firstPageNoOnPageList) }));
                        }
                  }

             // 100 건 이상인 경우만 "다음" 페이지 표시
             if(selectListSize > 100){
                  sb.append(MessageFormat. format( nextPageLabelnew Object[] { jsFunction, Integer.toString(firstPageNoOnPageList+paginationInfo.getPageSize()) ,  "\'PAGING_AFTER\'" })); // 다음 (처음 페이지 번호 + 페이지 사이즈)
            }
            
            sb.append( "</ul>\n");        
            
             return sb.toString();
      }

}








반응형