Spring 페이징을 위한 로직

김종태·2021년 11월 4일
1

게시판의 필수 요소 '페이징'

페이징은 웹페이지의 게시판 구조에서 빠질수 없는 요소중 하나이다
페이징을 적절히 활용하면 서버의 부하도 줄이고 클라이언트에게 엄청난
스크롤 압박에서 벗어날 수 있게 해준다 그렇기 때문에 페이징을 확실하게
이해하고 넘어가도록 하자

예시(네이버 쇼핑 페이징 구성)


사진은 네이버쇼핑 상품목록 페이지이다. 우리가 주의깊게 살펴보아야 하는 부분은
형광펜으로 표시된 pagingIndex=4&pagingSize=40 부분이다.
현재 4페이지 이며 상품목록을 40개 단위로 불러오겠다 라고 서버에게 전송하는 파라미터
부분이다. 우리는 이처럼 현재페이지 정보와, 목록사이즈 두가지 정보만을 서버에서
전달받아 페이징을 구성할 것이다.

구현

컨트롤러에 전송할 파라미터 객체

package com.example.demo.dto;

import lombok.Getter;

@Getter
public class PageParam {

	// 인스턴스생성시 원하는 기본값 설정
	private int page = 1;
	private int amount = 10;
	
    	//Mybatis Mapper에 바인딩 시 활용됨
	private int start = 0;
	
	public PageParam() {
	}
	
	public void setAmount(int amount) {
		log.warn("setAmount");
		this.amount = amount;
		this.start = (page-1)*amount;
	}
	
	public void setPage(int page) {
		log.warn("setPage");
		this.page = page;
		this.start = (page-1)*amount;
	}
}

기본적으로 page, amount를 전달받는 역할을 한다
page와 amount를 선택적으로 전송 할 수 있도록 객체 초기화시 초기값을
설정 해둔다.

페이지 화면에 전달할 Paging Model

package com.example.demo.dto;

import lombok.Getter;

@Getter
public class PageDTO {

	private int endPage;
	private int startPage;
	private int realEnd;
	private int total;
	
	boolean prev, next;
	
    	//앞서 작성한 PageParam 객체를 필드로 사용한다
	PageParam pageParam;

	//pageParam을 파라미터로 받아와 페이지 연산에 활용한다
	public PageDTO(PageParam pageParam, int total) {
		
		this.pageParam = pageParam;
		this.total = total;
		
		int current = pageParam.getPage();
		int amount = pageParam.getAmount();
		
        	// 페이징의 끝번호 구하기!
            	// Math.ceil은 소숫점 자리에서 올림을 한다
		this.endPage = (int)( Math.ceil(current*0.1))*10;
        
        	// 페이징의 시작번호 구하기!
        	// (현재 보이는 페이지의 끝번호) - (한 화면에 보여질 페이지 개수 - 1) 
		this.startPage = endPage-9; 
		
		this.realEnd = (int)Math.ceil(total/amount);
		
        	// 실제 끝번호 보다 endPage가 큰경우 실제 번호로 대입한다
		if(realEnd < endPage) {
			this.endPage = realEnd;
		}
		
		this.prev = current > 1;
		this.next = current < realEnd;
	}
}

이 객체가 실제 페이지 화면으로 전달될 페이지 Model이다
우리에게 주어진 속성은 현재페이지, 목록갯수, 총목록갯수 이렇게
세가지다. 이걸 기준으로 endPage, startPage, (prev, next)-이전,다음 버튼 표시 여부
를 계산해야 한다. 계산을 하는데 있어서 가장 핵심적으로 알아야 할 부분은
끝번호를 먼저 계산 하는것 이것이 가장 중요한 부분이다

시작번호는 끝번호에 (표시되는 페이지의 갯수 -1) 만큼 빼주기만 하면 계산이 끝난다

이러한 구성의 코드를 처음 본다면 의아한 부분이 존재한다
endPage?? realEnd?? 왜 끝번호가 두개나 있는건지 의아할 것이다

만약 15페이지 구성에 현재페이지가 11페이지 라면 endPage=20, realEnd=15가 될것이다
위 예시에 상황이라면 페이지가 20페이지 까지 표시가 되어버릴것이다 그렇기 때문에 실제 끝번호
보다 endPage가 큰 경우라면 endPage에 realEnd를 대입시켜 예외처리를 한다

SQL 처리

SELECT * FROM board b LEFT JOIN member m
ON b.writer_id = m.member_id 
ORDER BY b.board_no DESC
LIMIT #{start}, #{amount}

목록을 전부 조회할때와 동일하지만 조회 해야할 시작번호와 조회할 목록 크기를 바인딩하여
LIMIT을 걸어 컬럼 수를 제한한다 MySQL을 사용중이기 때문에 LIMIT을 사용했지만
오라클을 포함한 다른 RDB들은 제각각 페이징을 처리하는 방법이 다르다 ORM을 사용하지 않는다면 직접 검색을 통해 가장 성능이 좋은 방법을 찾아내자

Controller

	@GetMapping("/list")
	public void list(PageParam page, Model model) {
		
		int total =  boardService.getTotalCount(page);
		PageDTO pageDTO = new PageDTO(page, total);
		
		model.addAttribute("boardList", boardService.getListPaging(page));
		model.addAttribute("page", pageDTO);
	}

컨트롤러 에서는 전달받은 page 요청 파라미터와, 미리 구현해둔 목록의 총 개수를 구해 PageDTO 객체를 생성하고, 페이징 처리된 목록리스트를 받아와 Model에 담아 뷰로 전달한다.

View(.jsp)

<nav aria-label="Page navigation example" class="d-flex justify-content-end">
  <ul class="pagination">
  	<c:if test="${page.prev }">						  
	    <li class="page-item"><a href="${contextPath }/board/list?page=${page.pageParam.page-1}" class="page-link">이전</a></li>
	</c:if>

	<c:forEach var="pageNum" begin="${page.startPage }" end="${page.endPage }" step="1">
	    <li class="page-item ${pageNum == page.pageParam.page? "active" : "" }"><a class="page-link" href="?page=${pageNum }">${pageNum }</a></li>
	</c:forEach>

  	<c:if test="${page.next }">						  
	    <li class="page-item"><a href="${contextPath }/board/list?page=${page.endPage+1}" class="page-link">다음</a></li>
	</c:if>							  
    </ul>
</nav>

전달받은 Model로 부터 페이지 버튼들을 생성하는 코드를 작성한다

<li class="page-item ${pageNum == page.pageParam.page? "active" : "" }">

위 코드는 현재페이지와 일치하다면 버튼의 색을 바꾸는 class를 추가하는 삼항 연산자이다.
jsp는 이런식으로 인라인 요소들을 입맛대로 추가해서 사용할 수 있는 좋은템플릿 엔진이다 tymleaf 등 스프링에서 제공하는 템플릿 엔진이 많지만 jsp가 많이 채택되는건 taglib를 통해 여러가지 기능들을 제공하고 가독성도 뛰어나기 때문에 (+익숙한 태그명을 사용하기 때문에 사용이 간편하다 if, forEach 등)아직까지도 보편적으로 사용되는것 같다.

페이징은
'끝번호를 먼저 구한다' 를 잘 기억해 두면 개발이 훨씬 순조로워 진다

profile
기록하며 성장하는 개발자 김종태

0개의 댓글