개발/[Spring] 블로그 만들기

[코드로 배우는 스프링 웹 프로젝트] 14강. 페이징 화면 처리 (페이지네이션, 페이징처리, JQuery)

ee2ee2 2022. 2. 13. 01:22
728x90
반응형

해당 프로젝트는 코드로 배우는 스프링 웹 프로젝트(개정판)을 기반으로 진행됩니다.


URL의 파라미터를 이용해서 정상적으로 원하는 페이지로 이동하는 것을 확인했다면, 화면 밑에 페이지를 표시하고 클릭할 수 있도록 진행해본다.

아래와 같은 과정으로 진행한다.

  • 브라우저 주소창에서 페이지 번호를 전달해서 결과를 확인하는 단계
  • JSP 에서 페이지 번호를 출력하는 단계
  • 각 페이지 번호에 클릭 이벤트 처리
  • 전체 데이터 개수를 반영해서 페이지 번호 조절

14.1 페이징 처리 할 때 필요한 정보들

- 현재 페이지 번호 (page)

- 이전과 다음으로 이동 가능한 링크의 표시 여부 (prev, next)

- 화면에서 보여지는 페이지의 시작 번호와 끝 번호 (startPage, endPage)


14.1.1 끝 페이지 번호와 시작 페이지 번호

페이징 처리를 하기 위해서는 가장 먼저 현재 사용자가 보고 있는 페이지의 정보이다.

예를 들어 내가 보고 있는 페이지가 5페이지면 화면의 페이지번호는 1부터 시작하지만, 15페이지를 본다면 11부터 시작해야하기 때문이다. (화면에 페이지 번호를 10개씩 보여준다고 가정할 때) 

페이지의 시작/끝 번호 구하는 공식

//10개씩 보여진다고 가정할 때
this.endPage = (int) (Math.ceil(현재 페이지 번호 / 10.0)) * 10;
this.startPage = this.endPage - 9;

단, endPage의 경우 위와 같이 계산하면 안되는 경우가 있다. 전체 데이터 수가 적을 경우 10페이지가 안되는 경우도 발생하기 때문이다. 즉, 끝 번호(endPage)는 전체 데이터 개수의 영향을 받는다. 

total을 통한 endPage 재계산

int realEnd = (int) (Math.ceil((total * 1.0) / 한 페이지당 출력되는 데이터 수));
		
if(realEnd < this.endPage) {
	this.endPage = realEnd;
}

이전(prev)와 다음(next) 계산

this.prev = this.startPage > 1;
this.next = this.endPage < realEnd;

14.2 페이징 처리를 위한 클래스 설계

org.zerock.domain 패키지에 PageDTO 클래스 생성한다.

PageDTO.java

: Criteria와 전체 데이터 수(total)을 파라미터로 지정한다. Criteria 안에는 페이지에서 보여주는 데이터(amount) 및 현재 페이지 번호(pageNum)을 갖고 있기 때문에 이를 이용해서 필요한 모든 내용을 계산할 수 있다. 

package org.zerock.domain;

import lombok.Getter;
import lombok.ToString;

@Getter
@ToString
public class PageDTO {
	
	private int startPage;
	private int endPage;
	private boolean prev, next;
	
	private int total;
	private Criteria cri;
	
	public PageDTO(Criteria cri, int total) {
		this.cri = cri;
		this.total = total;
		
		this.endPage = (int) (Math.ceil(cri.getPageNum() / 10.0)) * 10;
		this.startPage = this.endPage - 9;
		
		int realEnd = (int) (Math.ceil((total * 1.0) / cri.getAmount()));
		
		if(realEnd < this.endPage) {
			this.endPage = realEnd;
		}
		
		this.prev = this.startPage > 1;
		this.next = this.endPage < realEnd;
	}
}

 

BoardController에서는 PageDTO를 사용할 수 있도록 Model에 담아서 화면에 전달할 수 있도록 수정한다.

BoardController 클래스의 list()

: PageDTO를 구성하려면 전체 데이터 개수(total)가 필요한데, 아직 처리 전이므로 임의의 값을 지정하였다.

//페이지 및 읽을 게시글 개수에 따른 조회 메소드
@GetMapping("/list")	
public void list(Criteria cri, Model model) {
	log.info("list : " + cri);
					
	model.addAttribute("list", service.getList(cri));			
	model.addAttribute("pageMaker", new PageDTO(cri, 123));
}

14.3 JSP에서 페이지 번호 출력

: 기존 게시글 리스트를 보이는 테이블 아래에 페이지 처리를 할 수 있는 번호를 추가한다.

views/board/list.jsp 일부

...생략
                                		<td><fmt:formatDate pattern="yyyy-MM-dd" value="${board.cdate}"/> </td>
                                		<td><fmt:formatDate pattern="yyyy-MM-dd" value="${board.udate}"/> </td>
                                	</tr>
                                </c:forEach>  
                            </table>
                            <!-- /.table-responsive -->
                            
                            <!-- start Pagination -->
                            <div class='pull-right'>
                            	<ul class="pagination">
                            	
                            		<c:if test="${pageMaker.prev}">
                            			<li class="paginate_button previous"> <a href="#">Previous</a> </li>
                            		</c:if>
                            		
                            		<c:forEach var="num" begin="${pageMaker.startPage}" end="${pageMaker.endPage}">
                            			<li class="paginate_button"> <a href="#">${num}</a> </li>
                            		</c:forEach>
                            		
                            		<c:if test="${pageMaker.next}">
                            			<li class="paginate_button next"> <a href="#">Next</a> </li>
                            		</c:if>
                            		
                            	</ul>
                            </div>
                            <!-- end Pagination -->

실행 결과

: http://localhost:8080/board/list?pageNum=4


14.3.1 페이지 번호 이벤트 처리

: 화면에서 페이지 번호가 보이기는 하지만, 아직 번호를 클릭했을 때의 이벤트 처리는 하지 않았다. JavaScript를 통해서 처리하는 방식을 이용해 본다.

1) 우선 페이지와 관련된 <a> 태그의 href 속성값으로 페이지 번호를 가지도록 수정한다.

list.jsp 일부 수정

                            <!-- start Pagination -->
                            <div class='pull-right'>
                            	<ul class="pagination">
                            	
                            		<c:if test="${pageMaker.prev}">
                            			<li class="paginate_button previous"> 
                            				<a href="${pageMaker.startPage - 1 }">Previous</a>
                            			</li>
                            		</c:if>
                            		
                            		<c:forEach var="num" begin="${pageMaker.startPage}" end="${pageMaker.endPage}">
                            			<li class="paginate_button ${pageMaker.cri.pageNum == num ? "active" : "" } "> 
                            				<a href="${num}">${num}</a>
                            			</li>
                            		</c:forEach>
                            		
                            		<c:if test="${pageMaker.next}">
                            			<li class="paginate_button next"> 
                            				<a href="${pageMaker.endPage + 1 }">Next</a>
                            			</li>
                            		</c:if>
                            		
                            	</ul>
                            </div>
                            <!-- end Pagination -->

실행 결과

: 아래와 같이 <a> 태그는 href 속성값으로 단순히 번호만을 가지게 된다.

3번 버튼 클릭했을 때,

따라서, <a> 태그가 원래의 동작을 못하도록 JavaScript로 처리하고 실제 페이지를 클릭하면 동작하는 부분은 별도의 <form> 태그를 이용해서 처리하도록 한다.

list.jsp 일부 

..생략

<form id='actionForm' action="/board/list" method='get'>
     <input type='hidden' name='pageNum' value = '${pageMaker.cri.pageNum}'>
     <input type='hidden' name='amount' value = '${pageMaker.cri.amount}'>
</form>

... 생략

<script type="text/javascript">
	$(document).ready(function(){
		var result= '<c:out value="${result}"/>';
		
		checkModal(result);
		
		history.replaceState({}, null, null); //뒤로가기시에 모달창이 또 뜨는 것을 방지하기 위함.
		
		function checkModal(result){
			if(result === '' || history.state){
				return;
			}
			
			if(result === 'success'){
				$(".modal-body").html("정상적으로 처리 되었습니다.");
				$("#myModal").modal("show");
				return;
			}
			
			if(parseInt(result) > 0){
				$(".modal-body").html("게시글 "+ parseInt(result) + "번이 등록되었습니다.");
				$("#myModal").modal("show");
				return;
			}
		}
		
		$("#regBtn").on("click", function() {
			self.location ="/board/register";
		});
		
		var actionForm = $("#actionForm");
		
		$(".paginate_button a").on("click", function(e) {
			e.preventDefault();	//<a> 태크를 클릭해도 페이지 이동이 없도록 하는 함수
			
			console.log('Click');
			
			actionForm.find("input[name='pageNum']").val($(this).attr("href"));	//현재 선택한 번호의 href값을 가져와 pageNum값으로 세팅한다.
			actionForm.submit();
		});
	
</script>

실행 결과

목록 화면에서 페이지 번호를 클릭하면 정상적으로 원하는 페이지로 이동하는 것을 확인할 수 있다.

클릭한 페이지 번호 따라 &lt;form&gt; 태그 내 페이지 번호가 변경됨을 확인

 


14.4 조회 페이지로 이동

: 목록 화면에서 페이지 번호 이동까지 확인하였다. 다만, 몇 가이 문제를 더 해결해야 한다. 사용자가 3페이지에 있는 게시글을 클릭한 후, 다시 목록으로 이동해보면 무조건 1페이지로 이동하게 된다. (이전에 보고 있던 3페이지가 아닌!)

이유는 페이지 번호를 기억하고 있지 않아서!

 

이를 해결하기 위해서는 조회 페이지로 갈 때 현재 목록 페이지의 pageNum과 amount를 같이 전달할 필요가 있다.

페이지 이동에 사용했던 <form> 태그에 추가로 게시물의 번호를 같이 전송하고 action값을 조정해서 처리해야 한다.

<c:forEach items="${list}" var="board">
     <tr>
        <td><c:out value="${board.bno}"/> </td>
        <%-- 수정 전 --%>
        <td><a href='/board/get?bno=<c:out value="${board.bno}"/>'><c:out value="${board.title}"/></a></td>
        
        <%-- 수정 후 : class = 'move' 추가--%>
        <td><a class='move' href='<c:out value="${board.bno}"/>'><c:out value="${board.title}"/></a></td>
        
... 생략

list.jsp 게시물 조회를 위한 이벤트 처리 추가


<script type="text/javascript">
	$(document).ready(function(){
    
    ..생략
    
		$(".move").on("click", function(e) {
			e.preventDefault(); //<a> 태크를 클릭해도 페이지 이동이 없도록 하는 함수
			actionForm.append("<input type='hidden' name='bno' value='"+$(this).attr("href")+"'>"); //현재 선택한 태그의 href 속성에 적혀져 있는 값을 가져와 bno값으로 세팅한다.
			actionForm.attr("action", "/board/get"); //action 값 변경
			actionForm.submit();
		});
        
        
    ..생략

실행 결과

: 게시글 조회(get) 시, pageNum, amount 값을 전달하는 것을 확인할 수 있다.

 


14.4.1 조회 페이지에서 다시 목록 페이지로 이동 - 페이지 번호 유지

조회 페이지에서 목록 페이지로 이동하기 위한 파라미터들이 같이 전송되었다면 조회 -> 목록으로 이동하기 위한 이벤트를 처리해야한다. BoardController의 get() 메서드의 파라미터를 Criteria도 받도록 변경해보자.

 

BoardController.java 일부

	//글 조회, 수정 메소드
	//@ModelAttribute : 자동으로 Model에 데이터를 지정한 이름으로 담아줌
	@GetMapping({"/get", "/modify"})
	public void get(@RequestParam("bno") Long bno, Model model, @ModelAttribute("cri") Criteria cri) {
		log.info("/get or /modify..");
		
		model.addAttribute("board", service.get(bno));
		
	}

views/board/get.jsp 일부

<form id='operForm' action="/board/modify" method="get">
	<input type="hidden" id='bno' name='bno' value='<c:out value="${board.bno }"/>'>
	<input type="hidden" id='pageNum' name='pageNum' value='<c:out value="${cri.pageNum }"/>'>
	<input type="hidden" id='amount' name='amount' value='<c:out value="${cri.amount }"/>'>
</form>

 

실행결과

: pageNum=8, amount=20의 값을 list 버튼을 눌러도 그대로 유지하여 파라미터 전달하는 것을 확인할 수 있음.

list 페이지에서도 값을 유지함.


14.4.2 조회 페이지에서 수정/삭제 페이지로 이동

: 조회 페이지에서는 'Modify' 버튼을 통해서 수정/삭제 페이지로 이동하게 된다. 수정/삭제 페이지에서는 다시 목록으로 가는 버튼이 존재하므로 동일하게 목록페이지에 필요한 파라미터들을 처리해야한다. 

BoardController에서는 get() 메서드에소 /get과 /modify를 같이 처리하므로 별도 추가 작업없이 Criteria를 Model에 담아 자동으로 전송된다.


14.5 수정과 삭제처리

modify.jsp에서는 <form> 태그를 이용하여 데이터를 처리한다.

views/board/modify.jsp 일부

: modify.jsp 역시 Criteria를 Model에서 사용하기 때문에 아래와 같이 태그를 만들어서 <form> 태그 전송에 포함한다.

 <form role="form" action="/board/modify" method="post" >
	<-- 추가 -->
	<input type="hidden" id='pageNum' name='pageNum' value='<c:out value="${cri.pageNum }"/>'>
	<input type="hidden" id='amount' name='amount' value='<c:out value="${cri.amount }"/>'>

14.5.1 수정/삭제 처리 후 이동

: POST 방식으로 진행하는 수정과 삭제 처리는 BoradController에서 각각의 메서트 형태로 구현되어 있으므로 페이지 관련 파라미터들을 처리하기 위해서는 변경이 필요하다.

BoardController의 modify()

	//글 수정 메소드
	@PostMapping("/modify")
	public String modify(BoardVO board, RedirectAttributes rttr, @ModelAttribute("cri") Criteria cri) {
		log.info("modify.." + board);
		
		if(service.modify(board)) {
			rttr.addFlashAttribute("result", "success");
		}
		
		rttr.addAttribute("pageNum", cri.getPageNum());
		rttr.addAttribute("amount", cri.getAmount());
		
		return "redirect:/board/list";
	}

BoardController의 remove()

	//글 삭제 메소드
	@PostMapping("/remove")
	public String remove(@RequestParam("bno") Long bno, Model model, RedirectAttributes rttr, @ModelAttribute("cri") Criteria cri) {
		log.info("/remove..");
		
		if( service.remove(bno)) {
			rttr.addFlashAttribute("result", "success");
		}
		
		rttr.addAttribute("pageNum", cri.getPageNum());
		rttr.addAttribute("amount", cri.getAmount());
		
		return "redirect:/board/list" ;	
	}

14.5.1 수정/삭제 페이지에서 목록 페이지로 이동

: 페이지 이동의 마지막은 수정/삭제를 취소하고 다시 목록 페이지로 이동하는 것이다. 목록 페이지는 오직 PageNum과 amount만을 사용하므로 <form> 태그의 다른 내용은 삭제하고 필요한 내용만을 다시 추가하는 형태가 편하다.

modify.jsp JavaScript 부분

<script type="text/javascript">

	$(document).ready(function() {
		
		var formObj = $("form");
		
		$('button').on("click", function(e) {
			
			e.preventDefault();		// 기본 동작인 submit을 막고, 'data-oper' 속성을 이용해서 원하는 기능을 동작할 수 있도록 함. (마지막에 submit 처리)
			
			var operation = $(this).data("oper");
			
			console.log(operation);
			
			//변수의 타입까지 모두 값을 때
			if(operation === 'remove'){
			    formObj.attr("action", "/board/remove");
			} else if (operation === 'list'){
				//move to list 
				//self.location = "/board/list";   // self.location : 현재 페이지를 다른 페이지(URL)로 이동
				formObj.attr("action", "/board/list").attr("method", "get");
				
				//list 페이지로 이동할 때는 모든 값이 필요없고 pageNum, amount 값만 필요함
				var pageNumTag = $("input[name='pageNum']").clone();	//pageNum값을 복사하여 pageNumTag 변수에 저장
				var amountTag = $("input[name='amount']").clone();	//amount값을 복사하여 pageNumTag 변수에 저장
			
				//form 태그 내 모든 내용 삭제 후, 필요 태그(pageNum, amount)만 다시 추가
				formObj.empty();
				formObj.append(pageNumTag);
				formObj.append(amountTag);
			}
			
			formObj.submit();
		});	
		
	});
</script>

만일 사용자가 'list' 버튼을 클릭한다면 <form> 태그에서 필요한 부분만 잠시 복사(clone)해서 보관해 두고, <form> 태그 내의 모든 내용은 지워버린다(empty). 이후, 다시 필요한 탵그들만 추가해서 '/board/list'를 호출한다.


14.6 MyBatis에서 전체 데이터의 개수 처리

: 실제 모든 게시물의 수(total)을 구해서 PageDTO를 구설할 때 전달해보자.

 

BoardMapper.java (인터페이스)

package org.zerock.mapper;

import java.util.List;

import org.zerock.domain.BoardVO;
import org.zerock.domain.Criteria;

public interface BoardMapper {
	
	...생략
    
	//게시글의 전체 개수 구하기
	public int getTotalCount(Criteria cri);
	
}

BoardMapper.xml 일부

<!-- 게시글의 전체 개수 구하기 -->
<select id="getTotalCount" resultType="int">
	select count(*) 
	from tbl_board 
	where 
	<include refid="criteria"></include>
	bno > 0
</select>

 

BoardService와 BoardServiceImpl에서는 별도의 메소드를 작성해서 BoardMapper의 getTotalCount()를 호출하도록 한다.

BoardService 인터페이스 일부

package org.zerock.service;

import java.util.List;

import org.zerock.domain.BoardVO;
import org.zerock.domain.Criteria;

public interface BoardService {
	
	..생략
	
	public int getTotal(Criteria cri);
}

BoardServiceImpl 클래스 일부

..생략

public class BoardServiceImpl implements BoardService{

	..생략

	@Override
	public int getTotal(Criteria cri) {
		log.info("get total count");
		
		return mapper.getTotalCount(cri);
	}
}

 

BoardController에서는 BoardService 인터페이스를 통해서 getTotal()을 호출하도록 한다.

BoardController 클래스 일부

	//페이지 및 읽을 게시글 개수에 따른 조회 메소드
	@GetMapping("/list")	
	public void list(Criteria cri, Model model) {
		log.info("list : " + cri);
				
		model.addAttribute("list", service.getList(cri));
		
		int total = service.getTotal(cri);		
		log.info("total : " + total);
		
		//model.addAttribute("pageMaker", new PageDTO(cri, 123));
		model.addAttribute("pageMaker", new PageDTO(cri, total));
	}

 

이를 끝으로 게시물의 등록, 수정, 삭제, 조회, 페이징 처리가 끝이 났다!

남은 조건은 검색 조건을 이용하는 처리를 진행해보자!