스프링

 

스프링 부트 ( 2.7.0)    mybatis  사용

 

1.  A 유형  페이징 처리 방법

 

1) MySQLPageRequest 추가

import com.fasterxml.jackson.annotation.JsonIgnore;

import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class MySQLPageRequest {

	private int page;
	private int size;
	
	
	@JsonIgnore
	@ApiModelProperty(hidden=true)
	private int limit;
	
	
	@JsonIgnore
	@ApiModelProperty(hidden=true)
	private int offset;
	
	
}

 

2) MySQLPageRequestHandleMethodArgumentResolver 추가

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import lombok.extern.slf4j.Slf4j;

/**
 * 
 * MySQL 쿼리 페이징 LIMIT , OFFSET 값을 자동 계산하여 MysqlPageRequest 클래스 담아서 컨트롤러에서 받을 수 있게함.
 *
 */
@Slf4j
public class MySQLPageRequestHandleMethodArgumentResolver implements HandlerMethodArgumentResolver{

	
	private static final String DEFAULT_PARAMETER_PAGE="page";
	private static final String DEFAULT_PARAMETER_SIZE="size";
	private static final int DEFAULT_SIZE =20;
	
	

	@Override
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {		
		HttpServletRequest request=(HttpServletRequest) webRequest.getNativeRequest();
		
		//현재 페이지
		int page =NumberUtils.toInt(request.getParameter(DEFAULT_PARAMETER_PAGE));
		
		//리스트 갯수
		int offset =NumberUtils.toInt(request.getParameter(DEFAULT_PARAMETER_SIZE), DEFAULT_SIZE);
		
		//시작지점
		int limit=(offset * page) - offset;
		log.info("page : {} ", page);
		log.info("limit : {} , {} ", limit, offset);		
		
		return new MySQLPageRequest(page, offset, limit, offset);
	}

	
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return MySQLPageRequest.class.isAssignableFrom(parameter.getParameterType());
	}
	
	
}

 

3) PageRequestParameter 추가 

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/** 페이지 요청정보 파라미터 정보 */
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class PageRequestParameter<T> {

	private MySQLPageRequest pageRequest;
	private T parameter;
	
}

 

4 ) WebMvcConfig 에  다음 메소드 추가

	/** 페이징 처리 */	
	@Override
	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
		// 페이지 리졸버 등록
		resolvers.add(new MySQLPageRequestHandleMethodArgumentResolver());
	}
	
	

 

5) 컨트롤 사용예

	/**2.게시판 페이징 검색처리 목록리턴  - 페이징 검색처리 첫번째 방법 (WebMvcConfig 에 페이지 리졸버 등록 방식) */
	@GetMapping({"/pageSearchList"})
	@ApiOperation(value="목록조회 - WebMvcConfig 에 페이지 리졸버 등록 방식", notes="2.게시판 페이징 검색처리 목록리턴 ")
	public BaseResponse<List<BoardDTO>> paginationSearchList(
			@ApiParam  MySQLPageRequest pageRequest,
			@ApiParam  BoardSearchParameter parameter
			) throws Exception{
		log.info("1.pageRequest :" , pageRequest);
		PageRequestParameter<BoardSearchParameter> pageRequestParameter=new PageRequestParameter<BoardSearchParameter>(pageRequest, parameter);
		log.info("2.pageRequestParameter :" , pageRequestParameter);
		return new BaseResponse<List<BoardDTO>>(boardService.paginationSearchList(pageRequestParameter));
	}

 

6) mybatis 사용예

   <select id="paginationSearchList" 	resultType="kr.so.songjava.mvc.domain.entity.Board">
    SELECT
    	 B.BOARD_SEQ,
    	 B.BOARD_TYPE,
    	 B.TITLE,
    	 B.REG_DATE    	 
   FROM T_BOARD B 
   <where>
   	 <if test="@org.apache.commons.lang3.StringUtils@isNotEmpty(parameter.keyword)">
   	 	AND B.TITLE LIKE CONCAT('%',  #{parameter.keyword}, '%')
   	 </if>
   	 
   	 <if test="@org.apache.commons.lang3.ObjectUtils@isNotEmpty(parameter.boardTypes)">
   	 	AND B.BOARD_TYPE IN (
   	 		<foreach collection="parameter.boardTypes" item="value" separator=",">
   	 			#{value}
   	 		</foreach>
   	 	)
   	 </if>   	 
   </where>   
   ORDER BY B.REG_DATE DESC     
   LIMIT #{pageRequest.limit} , #{pageRequest.offset}
  </select>

 

7) 출력예

{
  "code": "SUCCESS",
  "message": null,
  "pageMaker": null,
  "displayPageNum": 0,
  "data": [
    {
      "boardSeq": 256,
      "boardType": {
        "code": "FAQ",
        "label": "자주묻는질문"
      },
      "title": "wJmTCEydrm",
      "contents": null,
      "regDate": "2022-06-26T03:38:59.000+00:00"
    },
    {
      "boardSeq": 512,
      "boardType": {
        "code": "FAQ",
        "label": "자주묻는질문"
      },
      "title": "JQvHGGREkU",
      "contents": null,
      "regDate": "2022-06-26T03:38:59.000+00:00"
    }
  ]
}

 

 

2.  B 유형  페이징 처리 방법  ( 전체 게시글 갯수 구함 )

1) MysqlPageMaker 추가

import java.util.List;

import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import com.fasterxml.jackson.annotation.JsonIgnore;

import io.swagger.annotations.ApiModelProperty;
import kr.so.songjava.mvc.domain.enums.BoardTypeInsert;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
 
/** MySQL PageMaker **/

@Getter
@Setter
@AllArgsConstructor
@ToString
public class MysqlPageMaker {
	
    /** mysql 들어갈 값은 page 가 아니라 pageStart, perPageNum 이다. //limit #{pageStart}, #{perPageNum} */    
    private int page;
    private int perPageNum;
   
	@JsonIgnore
	@ApiModelProperty(hidden=true)
    private int pageStart;
     
    /** 하단 페이징  << 1 2 3 4 5 6 7 8 9 10 >>  */
	@JsonIgnore
	@ApiModelProperty(hidden=true)
    private int totalCount; //전체 개수
    
	@JsonIgnore
	@ApiModelProperty(hidden=true)
    private int startPage; // 시작 페이지
	
	@JsonIgnore
	@ApiModelProperty(hidden=true)
    private int endPage;   // 끝페이지
	
	@JsonIgnore
	@ApiModelProperty(hidden=true)
    private boolean prev;  // 이전 여부
	
	@JsonIgnore
	@ApiModelProperty(hidden=true)
    private boolean next;  // 다음 여부
    
	@JsonIgnore
	@ApiModelProperty(hidden=true)
    private int displayPageNum;
    
	@JsonIgnore
	@ApiModelProperty(hidden=true)
    private int tempEndPage; //마지막 페이지
     
    /** 검색처리 추가 */
    private String searchType;  //NOTICE, FAQ,INQUIRY    
    private String keyword;
     
     
 
    
    public MysqlPageMaker() {
        this.page=1;          //초기 페이지는 1 
        this.perPageNum=10;  //limit 10 개씩 보여준다.
        this.displayPageNum=6; //초기 6개
    }
     
     
    public void setPage(int page) {
        //페이지 번호가 0이거나 0보다 작으면 1페이지로 한다.
        if(page <=0){
            this.page=1;
            return;
        }
        this.page = page;
    }
         
    /** MyBatis SQL 의 Mapper 에서 인식해서 가져가는 파라미터 값 메소드 #{perPageNum} */
    public void setPerPageNum(int perPageNum) {
        //몇개 씩 보여줄것인가 이다. 최대 100개씩 보여 줄것으로 설정한다.
        //만약 0보다 작거나 100 보다 크면 10으로 초기화 시킨다.
        if(perPageNum <=0 || perPageNum >100){
            this.perPageNum=10;
            return;
        }
        this.perPageNum = perPageNum;
    }
 
 
    /**  MyBatis SQL 의 Mapper 에서 인식해서 가져가는 파라미터 값 메소드 #{pageStart} */
    public int getPageStart() {
        //실질적으로 Mybatis 에서  파라미터로 인식해서  가져오는 것은 get 이다.
        // 따라서 getPageStart 에서 값을 설정한다.
        //시작 데이터 번호 = (페이지 번호 -1 ) * 페이지당 보여지는 개수
        this.pageStart=(this.page -1)*perPageNum;
        return this.pageStart;
    }
 
     
    //전체 페이지 설정과 동시에 하단에 뿌려질 페이지 계산하기 
    public void setTotalCount(int totalCount) {
        this.totalCount = totalCount;
        calcData();
    }
     
     
    private void calcData(){
        //현재 페이지 번호 / 하단 페이지번호 수
        endPage=(int)(Math.ceil(page / (double)displayPageNum)*displayPageNum);
         
        startPage=(endPage - displayPageNum) +1;
         
        tempEndPage=(int)(Math.ceil(totalCount/(double)perPageNum));
         
        if(endPage >tempEndPage){
            endPage=tempEndPage;
        }
         
        prev =startPage ==1 ? false :true;
        next =endPage *perPageNum >=totalCount ? false :true;
    }
     
     
    /** 일반적인 페이징 처리 파라미터 출력 데이터 ex) /memberList?page=4&perPageNum=10 */
    public String makeQuery(int page){
        UriComponents uriComponents=
                UriComponentsBuilder.newInstance()
                .queryParam("page", page)
                .queryParam("perPageNum", perPageNum)
                .build();
        return uriComponents.toUriString();
    }
     
    /** 일반적인 페이징 부트스트랩 출력 */
    public String bootStrapPagingHTML(String url){
       StringBuffer sBuffer=new StringBuffer();
       sBuffer.append("<ul class='pagination'>");
       if(prev){
           sBuffer.append("<li><a href='"+url+makeQuery(1)+"'>처음</a></li>");
       }
        
       if(prev){
           sBuffer.append("<li><a href='"+url+makeQuery(startPage-1)+"'>&laquo;</a></li>");
       }
 
        String active="";
        for(int i=startPage; i <=endPage; i++){
            if(page==i){
                 active="class=active";
            }else{
                active="";
            }
            sBuffer.append("<li " +active+" >");
            sBuffer.append("<a href='"+url+makeQuery(i)+"'>"+i+"</a></li>");
            sBuffer.append("</li>");
        }
         
        if(next && endPage>0){
            sBuffer.append("<li><a href='"+url+makeQuery(endPage+1)+"'>&raquo;</a></li>");          
        }
         
        if(next && endPage>0){
            sBuffer.append("<li><a href='"+url+makeQuery(tempEndPage)+"'>마지막</a></li>");            
        }       
         
        sBuffer.append("</ul>");  
        return sBuffer.toString();
    }
     
     
    /** 검색 추가 페이지 파라미터  */
    public String makeSearch(int page){
        UriComponents uriComponents=
                UriComponentsBuilder.newInstance()
                .queryParam("page", page)
                .queryParam("perPageNum", perPageNum)
                .queryParam("searchType", searchType)
                .queryParam("keyword", keyword)
                .build();
        return uriComponents.toUriString();
    }
     
     
     
    /** 검색 추가 페이징 부트스트랩 출력 */
    public String bootStrapPagingSearchHTML(String url){
       StringBuffer sBuffer=new StringBuffer();
       sBuffer.append("<ul class='pagination'>");
       if(prev){
           sBuffer.append("<li><a href='"+url+makeSearch(1)+"'>처음</a></li>");
       }
        
       if(prev){
           sBuffer.append("<li><a href='"+url+makeSearch(startPage-1)+"'>&laquo;</a></li>");
       }
 
        String active="";
        for(int i=startPage; i <=endPage; i++){
            if(page==i){
                 active="class=active";
            }else{
                active="";
            }
            sBuffer.append("<li " +active+" >");
            sBuffer.append("<a href='"+url+makeSearch(i)+"'>"+i+"</a></li>");
            sBuffer.append("</li>");
        }
         
        if(next && endPage>0){
            sBuffer.append("<li><a href='"+url+makeSearch(endPage+1)+"'>&raquo;</a></li>");         
        }
         
        if(next && endPage>0){
            sBuffer.append("<li><a href='"+url+makeSearch(tempEndPage)+"'>마지막</a></li>");           
        }       
         
        sBuffer.append("</ul>");  
        return sBuffer.toString();
    }
     
    
    /** JSON  반환 처리를 위해  */
    public MysqlPageMakerResponse  toResPageMaker(MysqlPageMaker pageMaker) {
    	return MysqlPageMakerResponse.builder()
    	.page(pageMaker.getPage())
    	.perPageNum(pageMaker.getPerPageNum())
    	.pageStart(pageMaker.getPageStart())
    	.totalCount(pageMaker.getTotalCount())
    	.startPage(pageMaker.getStartPage())
    	.endPage(pageMaker.getEndPage())
    	.prev(pageMaker.isPrev())
    	.next(pageMaker.isNext())
    	.displayPageNum(pageMaker.getDisplayPageNum())
    	.tempEndPage(pageMaker.getTempEndPage())
    	.searchType(pageMaker.getSearchType())
    	.keyword(pageMaker.getKeyword())
    	.build();
    }
 
      
     
}

 

 

2)MysqlPageMakerResponse 추가

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;

/** 반환 처리시 */
@Setter
@Getter
@AllArgsConstructor
@ToString
@Builder
public class MysqlPageMakerResponse {
	
    private int page;
    
    private int perPageNum;
  
    private int pageStart;
     
    private int totalCount; //전체 개수
    
    private int startPage; // 시작 페이지
	
    private int endPage;   // 끝페이지
	
    private boolean prev;  // 이전 여부
	
    private boolean next;  // 다음 여부
    
    private int displayPageNum;
    
    private int tempEndPage; //마지막 페이지     
    
    private String searchType;
    
    private String keyword;
     
       
}

 

 

3) 컨트롤 사용예 

	/** 3. 게시판 페이징 검색처리 목록리턴 - 페이징 검색처리 두번째방법 (전체 갯수 구함) */
	@GetMapping({"/pageSearchList2"})
	@ApiOperation(value="목록조회 - MysqlPageMaker 사용 ", notes="3.게시판 페이징 검색처리 목록리턴 ")
	public BaseResponse<List<BoardDTO>> paginationSearchList2(
			@ApiParam  MysqlPageMaker pageMaker
			) throws Exception{
		
		int totalCount =boardService.getTotalCount(pageMaker);
		pageMaker.setTotalCount(totalCount);
		
		log.info("1.pageMaker :  {} " , pageMaker);
		log.info("2.totalCount :  {}" , totalCount);		
		return new BaseResponse<List<BoardDTO>>(boardService.paginationSearchList2(pageMaker), pageMaker);
	}
	

 

4) 반환처리  BaseResponse  

import kr.so.songjava.utils.pagination2.MysqlPageMaker;
import kr.so.songjava.utils.pagination2.MysqlPageMakerResponse;
import lombok.Data;

@Data
public class BaseResponse<T> {

	private BaseResponseCode code;
	private String message;
	private MysqlPageMakerResponse pageMaker;
	private int displayPageNum;
	
	private T data;

	public BaseResponse(T data) {		
		this.code=BaseResponseCode.SUCCESS;
		this.data=data;
	}
	
	public BaseResponse(String status, T data) {
		if(status.equals("error"))this.code=BaseResponseCode.ERROR;		
		else this.code=BaseResponseCode.SUCCESS;
		this.data=data;
	}
	
	public BaseResponse(String status, T data, String message) {
		if(status.equals("error"))this.code=BaseResponseCode.ERROR;		
		else this.code=BaseResponseCode.SUCCESS;
		this.data=data;
		this.message=message;
	}

	/** MysqlPageMaker 페이지 처리 메소드 */
	public BaseResponse(T data,  MysqlPageMaker mysqlPageMaker) {
		this.code=BaseResponseCode.SUCCESS;
		this.data=data;
		this.pageMaker=mysqlPageMaker.toResPageMaker(mysqlPageMaker);
	}
	
	
	
	
}

 

5) BaseResponseCode

import lombok.NoArgsConstructor;

@NoArgsConstructor
public enum BaseResponseCode {

	SUCCESS(200), // 성공
	ERROR(500), // 실패
	LOGIN_REQUIRED,
	DATA_IS_NULL,
	VALIDATE_REQUIRED
	;
	
	private int status;
	
	
	BaseResponseCode(int status) {
		this.status = status;
	}
	
	public int status() {
		return status;
	}
	
}

 

Boad.xml

    <sql id="searchSql">
       <where>
	   	 <if test="@org.apache.commons.lang3.StringUtils@isNotEmpty(keyword)">
	   	 	AND B.TITLE LIKE CONCAT('%',  #{keyword}, '%')
	   	 </if>
	   	 
	   	 <if test="@org.apache.commons.lang3.ObjectUtils@isNotEmpty(searchType)">
	   		<if test="searchType == 'NOTICE'.toString()">
	   			AND B.BOARD_TYPE ='NOTICE'
	   		</if>

	   		<if test="searchType == 'FAQ'.toString()">
	   			AND B.BOARD_TYPE ='FAQ'
	   		</if>
	   		
	   		<if test="searchType == 'INQUIRY'.toString()">
	   			AND B.BOARD_TYPE ='INQUIRY'
	   		</if>

	   		<if test="searchType == 'NONE'.toString()">
	   			AND B.BOARD_TYPE ='NONE'
	   		</if>
	   			   			   			   		
	   	 </if>   	 
	   </where>       
    </sql>

  
  	<select id="getTotalCount" resultType="int">
  	  SELECT
	    	 COUNT(B.BOARD_SEQ) AS cnt    	 
	   FROM T_BOARD B 
  		<include refid="searchSql"></include>
  	</select>
  
  
    <select id="paginationSearchList2" 	resultType="kr.net.macaronics.mvc.domain.entity.Board">
	    SELECT
	    	 B.BOARD_SEQ,
	    	 B.BOARD_TYPE,
	    	 B.TITLE,
	    	 (SELECT IF( B.DEL_YN ='Y', 'true', 'false')  ) as DEL_YN ,
	    	 B.REG_DATE    	 
	   FROM T_BOARD B 
	 
	    <include refid="searchSql"></include>
	 
	   ORDER BY B.REG_DATE DESC     
	   LIMIT #{pageStart} , #{perPageNum}
  </select>
  

 

http://localhost:8080/swagger-ui.html

 

 

 

출력 예

{
  "code": "SUCCESS",
  "message": null,
  "pageMaker": {
    "page": 1,
    "perPageNum": 3,
    "pageStart": 0,
    "totalCount": 1001,
    "startPage": 1,
    "endPage": 6,
    "prev": false,
    "next": true,
    "displayPageNum": 6,
    "tempEndPage": 334,
    "searchType": null,
    "keyword": null
  },
  "displayPageNum": 0,
  "data": [
    {
      "boardSeq": 256,
      "boardType": {
        "code": "FAQ",
        "label": "자주묻는질문"
      },
      "title": "wJmTCEydrm",
      "contents": null,
      "regDate": "2022-06-26T03:38:59.000+00:00"
    },
    {
      "boardSeq": 512,
      "boardType": {
        "code": "FAQ",
        "label": "자주묻는질문"
      },
      "title": "JQvHGGREkU",
      "contents": null,
      "regDate": "2022-06-26T03:38:59.000+00:00"
    },
    {
      "boardSeq": 768,
      "boardType": {
        "code": "FAQ",
        "label": "자주묻는질문"
      },
      "title": "fPwypLLrpP",
      "contents": null,
      "regDate": "2022-06-26T03:38:59.000+00:00"
    }
  ]
}

 

 

 

소스

https://github.com/braverokmc79/sprig_boot_2.7.0_mybatis_board_project/commit/91982e583b2a2c9f51c32f5f1d0d12874bf953f3

 

 

 

 

 

 

 

 

 

about author

PHRASE

Level 60  라이트

인간의 의지에 저항할 수 있는 것은 없다. -디즈레일리

댓글 ( 4)

댓글 남기기

작성