Chapter 14 페이징 화면 처리 - oneso123456789/2022 GitHub Wiki

Feedback

게시글 삭제시 아무런 알림 없이 바로 list로 돌아가버림 모달창이라도 하나 띄우는게 좋을꺼 같음


Chapter 14 페이징 화면 처리

URL의 파라미터를 이용해서 정상적으로 원하는 페이지로 이동하는 것을 확인했다면,
화면 밑에 페이지 번호를 표시하고 사용자가 페이지 번호를 클릭할 수 있게 처리함

페이지를 보여주는 작업은 다음과 같은 과정을 통해서 진행함

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

페이지 처리는 단순히 링크의 연결이기 때문에 어렵지 않지만,
다음그림(p303)과 같이 목록 페이지에서 조회 페이지, 수정 삭제 페이지까지
페이지 번호가 유지되어야만 하기 때문에 끝까지 신경써야 하는 부분들이 많은편임 (그림은 페이지 번호가 어떤 작업을 하던 유지되면서 링크가 연결되는 모습임)

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

화면에 페이징 처리를 하기 위해서는 우선적으로 여러 가지 필요한 정보들이 존재
화면에 페이지는 크게 다음과 같은 정보들이 필요함

  • 현재 페이지 번호(page)
  • 이전과 다음으로 이동 가능한 링크의 표시 여부(prev, next)
  • 화면에서 보여지는 페이지의 시작 번호와 끝 번호(startPage, endPage)

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

페이징 처리를 하기 위해서 우선적으로 필요한 정보는 현재 사용자가 보고 있는 페이지(page)의 정보임
예를 들어, 사용자가 5페이지를 본다면 화면의 페이지 번호는 1부터 시작하지만,
사용자가 19페이지를 본다면 11부터 시작해야 하기 때문임
(화면에 10개씩 페이지 번호를 출력한다고 가정한 상태)

흔히들 페이지를 계산할 때 시작 번호를 먼저 하려고 하지만,
오히려 끝 번호를 먼저 계산해 두는 것이 수월함
끝 번호는 다음과 같은 공식으로 구할 수 있음(페이지 번호가 10개씩 보인다고 가정함)

페이징의 끝 번호(endPage)계산

this.endPage = int(Math.ceil(페이지번호 /10.0)) * 10;

Math.ceil()은 소수점을 올림으로 처리하기 때문에 다음과 같은 상황이 가능함

  • 1페이지의 경우 : Math.ceil(0.1) * 10 = 10
  • 10페이지의 경우 : Math.ceil(1) * 10 = 10
  • 11페이지의 경우 : Math.ceil(1.1) * 10 = 20

끝 번호(endPage)는 아직 개선의 여지가 있음
만일 전체 데이터 수가 적다면 10페이지로 끝나면 안되는 상황이 생길 수도 있기 때문임
그럼에도 끝 번호(endPage)를 먼저 계산하는 이유는 시작 번호(startPage)를 계산하기 수월하기 때문임

만일 화면에 10개씩 보여준다면 시작 번호(startPage)는 무조건 끝 번호(endPage)에서 9라는 값을 뺀 값이 됨

페이징의 시작 번호(startPage)계산

this.startPage = this.endPage - 9;



끝 번호(endPage)는 전체 데이터 수 (total)에 의해서 영향을 받음
예를 들어, 10개씩 보여주는 경우 전체 데이터 수(total)가 80개라고 가정하면
끝 번호(endPage)는 10이 아닌 8이 되어야만 함

만일 끝 번호(endPage)와 한 페이지당 출력되는 데이터 수(amount)의 곱이
전체 데이터 수(total) 보다 크다면 끝 번호(endPage)는 다시 total을 이용해서
다시 계산되어야함

total을 통한 endPage의 재계산

realEnd = (int) (Math.ceil((total * 1.0) / amount));

if(realEnd < this.endPage){
  this.endPage = realEnd;
}

먼저 전체 데이터 수(total)를 이용해서 진짜 끝 페이지(realEnd)가 몇 번까지 되는지를 계산
만일 진짜 끝 페이지(realEnd)가 구해둔 끝 번호(endPage)보다 작다면 끝 번호는
작은값이 되어야함

이전(prev)과 다음(next) 이전(prev)과 다음은 아주 간단히 구할 수 있음

이전(prev)의 경우는 시작번호(startPage)가 1보다 큰 경우라면 존재하게 됨

이전(prev)계산

this.prev = this.startPage > 1;

다음(next)으로 가는 링크의 경우 위의 realEnd가 끝 번호(endPage)보다 큰 경우에만
존재하게 됨

다음(next)계산

this.next = this.endPage < realEnd;

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

화면에 페이징 처리를 위해서 위와 같이 여러 정보가 필요하다면 클래스를 구성해서 처리하는
방식도 꽤 편한 방식이 될 수 있음

클래스를 구성하면 Controller 계층에서 JSP 화면에 전달할 때에도 객체를 생성해서
Model에 담아 보내는 과정이 단순해지는 점도 있음

com.crow.domain package에 PageDTO 클래스를 설계함

PageDTO 클래스

package com.crow.domain;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
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;
    }
}

PageDTO는 생성자를 정의하고 Criteria와 전체 데이터 수(total)를 파라미터로 지정함 Criteria 안에는 페이지에서 보여주는 데이터 수(amount)와 현재 페이지 번호(pageNum)를 가지고 있음
이를 이용해 필요한 모든 내용을 계산할 수 있음

BoardController에서는 PageDTO를 다음과 같이 사용할 수 있도록 Model에 담아서 화면에 전달해 줄 필요가 있음

따라서 GET list()메서드를 다음과 같이 수정함

BoardController클래스의 list()

    @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));
    }

코드를 보자면 list()는 pageMaker라는 이름으로 PageDTO 클래스에서 객체를 만들어서 Model에 담아줌
PageDTO를 구성하기 위해서는 전체 데이터 수가 필요한데,
아직 그 처리가 이루어지지 않았으므로 임의의 값으로 123을 지정했음


14.3 JSP에서 페이지 번호 출력

JSP에서 페이지 번호를 출력하는 부분은 JSTL을 이용해서 처리 할 수 있음

SBAdmin2는 부트 스트랩 기반으로 구성되어 있기 때문에
https://v4-alpha.getbootstrap.com/components/pagination와 같이 부트 스트랩 관련
링크들에 필요한 예제들이 존재함

예제는 SBAdmin2의 pages 폴더에 있는 tables.html 페이지의 페이지 처리를 이용해서 구성
기존의 <table>태그가 끝나는 직후에 페이지 처리를 추가함

views/board/list.jsp의 일부

<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 -->

Modal창의 아래쪽에 별도의 <div class='row'>를 구성하고 페이지 번호들을 출력함 pageMaker라는 이름으로 전달된 PageDTO를 이용해서 화면에 페이지 번호들을 출력해줌

예를 들어, 현재 total은 123이라는 숫자로 지정되어 있으므로 5페이지를 조회하면 next값은 true가 되어야함
하지만 amount값이 20인 경우에는 7페이지까지만 출력되어야하므로 next가 false임

14.3.1 페이지 번호 이벤트 처리

화면에서 페이지 번호가 보이긴 하지만 아직 페이지 번호를 클릭했을 때 이벤트 처리가 남아있음

일반적으로 <a>태그의 href 속성을 이용하는 방법을 사용할수도 있지만,
직접 링크를 처리하는 방식의 경우 검색 조건이 붙고 난 후에 처리가 복잡해짐
따라서 JavaScript를 통해서 처리하는 방식을 이용함

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

번호 출력부분은 <c:out>을 이용해서 출력하는 것이 좋지만 예제에선 가독성문제로
일반 EL을 이용했음

list.jsp 일부 수정

<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>

이제 화면에서는 <a>태그는 href 속성값으로 단순히 번호만을 가지게 변경됨
이 상태에서 페이지 번호를 클릭하면 해당 URL이 존재하지 않기 때문에 문제가 생기게 됨

<a>태그가 원래의 동작을 못하도록 JavaScript 처리를해주고
실제 페이지를 클릭하면 동작하는 부분은 별도의 <form> 태그를 이용해서 처리하도록함
(여기서도 <c:out>을 사용하는게 더 좋은 방법이지만 간단히 사용하기 위해서 EL로 처리함)

이정도라면 <c:out> 처리 방식을 한번 찾아서 해보라는 뜻으로 보임

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>

이 부분을 처리하다가 구문오류 발생함 5월 12일자 디버그 일지 참고

기존에 동작하던 JavaScript부분은 아래와 같이 기존의 코드에 페이지 번호를 클릭하면
처리하는 부분을 추가해줌

list.jsp의 일부

				$("#regBtn").on("click", function() {

					self.location = "/board/register";

				});

				var actionForm = $("#actionForm");

				$(".paginate_button a").on("click", function(e){

				    e.preventDefault();

				    console.log('click');
				    

				    actionForm.find("input[name='pageNum']")
				             .val($(this).attr("href"));
				    actionForm.submit();
					
				});

			});

var actionForm = $("#actionForm");부터 추가한 코드임

list.jsp에서는 <form>태그를 추가해서 URL의 이동을 처리하도록 변경함
JavaScript에서는 <a>태그를 클릭해도 페이지 이동이 없도록 preventDefault()처리를 하고,
<form> 태그 내 pageNum 값은 href 속성값으로 변경함

이 처리를 하고나면 화면에서 페이지 번호를 클릭했을때 <form>태그 내의 페이지 번호가 바뀌는것을 확인가능

그리고 마지막으로 actionForm 자체를 submit()시켜주면됨

브라우저에서 확인하면 제대로 이동이 가능해짐

c:out형식으로 구현하는걸 해봐야겠음

2022-05-12 14.4 조회페이지로 이동 전까지 함

14.4 조회 페이지로 이동

목록 화면에서 페이지 번호를 클릭하면 정상적으로 원하는 페이지로 이동하는 것을
볼 수 있지만, 몇 가지 문제가 생김
우선 사용자가 페이지에 있는 게시글을 클릭한 후 다시 목록으로 이동하면
무조건 1페이지 목록 페이지로 이동하는 현상이 있음

페이징 처리를 하고 나면 특정 게시물의 조회 페이지로 이동한 후 다시 목록으로
돌아가는데 문제가 생김(조회 페이지에서 List를 선택하면 다시 1페이지의 상태로 돌아감)

이를 해결하기 위해선 조회 페이지로 갈 때 현재 목록 페이지의 pageNum과 amount를 같이 전달해야함
이런 경우 페이지 이동에 사용했던 <form> 태그에 추가로 게시물의 번호를 같이 전송하고,
action 값을 조정해서 처리할 수 있음

원래 게시물의 제목에는 /board/get?bno=xxx로 이동할 수 있는 링크가 직접 처리되어 있음

list.jsp의 일부

<a href='/board/get?bno=<c:out value="${board.bno}"/>'>
<c:out value="${board.title}" /></a>

페이지 번호는 조회 페이지에서 전달되지 않기 때문에 조회 페이지에서 목록 페이지로
이동할 때는 아무런 정보가 없이 다시 /board/list를 호출함

간단하게 각 게시물의 링크에 추가로 &pageNum=xx와 같이 처리할 수도 있지만 나중에
여러 조건들이 추가되는 상황에서는 복잡한 링크를 생성해야함

<a>태그로 복잡한 링크를 생성하는 방식이 나쁘다고는 할 수 없음
가장 대표적인 예가 검색엔진임

검색 엔진은 출력된 정보와 링크를 저장해서 사용하기 때문에 <a>태그 내의
완전한 URL인 경우가 노출에 유리함
만일 웹페이지가 검색엔진에 의해서 노출이 필요한 경우라면 직접 모든 문자열을
구성해 주는 방식이 더 좋음

직접 링크로 연결된 경로를 페이지 이동과 마찬가지로 <form> 태그를 이용해서 처리할 것이므로
<a>태그에는 이동하려는 게시물의 번호만을 가지게 수정함
(이벤트 처리를 수월하게 하기 위해서 <a>태그에 class 속성을 하나 부여함

list.jsp일부

							<td>
							 <a class='move' href='<c:out value="${board.bno}"/>'>
								<c:out value="${board.title}" /></a>
							</td>



화면에서는 조회 페이지로 가는 링크 대신에 단순히 번호만이 출력됨
(마우스를 올려보면 아래쪽에서 확인이 가능함)


실제 클릭은 JavaScript를 통해서 게시물의 제목을 클릭했을 때 이동하도록 이벤트 처리를
새로 작성해줌

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

$(".move").on("click", function(e) {

			        e.preventDefault();
			        actionForm.append("<input type='hidden' name='bno'  value='"
					         + $(this).attr("href")+"'>");
					actionForm.attr("action","/board/get");
					actionForm.submit();
			    });

게시물의 제목을 클릭하면 <form> 태그에 추가로 bno 값을 전송하기 위해서
<input>태그를 만들어 추가하고, <form> 태그의 action은 /board/get으로 변경해줌

위의 처리가 정상적으로 되었다면 게시물의 제목을 클릭했을때
pageNum과 amount 파라미터가 추가로 전달되는 것을 볼 수 있음

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

조회 페이지에 다시 목록 페이지로 이동하기 위한 파라미터들이 같이 전송되었다면
조회 페이지에서 목록으로 이동하기 위한 이벤트를 처리해야함

BoardController의 get() 메서드는 원래는 게시물의 번호만 받도록 처리되어 있었지만,
추가적인 파라미터가 붙으면서 Criteria를 파라미터로 추가해서 받고 전달함

BoardController 클래스의 일부

    @GetMapping({ "/get", "/modify" })
    public void get(@RequestParam("bno") Long bno, @ModelAttribute("cri") Criteria cri, Model model) {

        log.info("BoardController /get or modify");
        model.addAttribute("board", service.get(bno));
    }

@ModelAttribute는 자동으로 Model에 데이터를 지정한 이름으로 담아줌
물론 @ModelAttribute를 사용하지 않아도 Controller에서 화면으로 파라미터가 된 객체는 전달됨,
하지만 좀 더 명시적으로 이름을 지정하기 위해서 사용해줌

기존 get.jsp에서는 버튼을 클릭하면

태그를 이용하는 방식이었으므로
필요한 데이터를 추가해서 이동하도록 수정함

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' name='pageNum'
						value='<c:out value="${cri.pageNum}"/>'> 
						<input type='hidden' name='amount'
						 value='<c:out value="${cri.amount}"/>'>
				</form>

기존 get.jsp는 operForm이라는 id를 가진

태그를 이미 이용했기 때문에 cri라는 이름으로
전달된 Criteria 객체를 이용해서 pageNum과 amount 값을 태그로 구성하고,
버튼을 클릭했을 때 정상적으로 목록 페이지로 이동하게 처리함

실제 동작시 get페이지에서 list페이지로 이동시 pageNum과 amount값이 같이 넘어가야함


14.4.2 조회 페이지

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

BoardController에서는 get() 메서드에서 /get/modify를 같이 처리하므로 별도의
추가적인 처리 없이도 Criteria를 Model에 cri라는 이름으로 담아서 전달함

조회 페이지에서 <form> 태그는 목록 페이지로의 이동뿐 아니라 수정/삭제 페이지
이동에도 사용하기 때문에 파라미터들은 자동으로 같이 전송됨


2022-05-13 14.5 수정과 삭제 처리전까지 함

14.5 수정과 삭제 처리

modify.jsp에서는

태그를 이용해서 데이터를 처리함
구현 방식은 거의 입력과 비슷함
pageNum과 amount 값이 <form>태그내에서 같이 전송할 수 있게 수정

views/board/modify.jsp의 일부

<div class="panel-heading">Board Read Page</div>
			<!-- /.panel-heading -->
			<div class="panel-body">

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

modifiy.jsp 역시 Criteria를 Model에 사용하기 때문에 위와 같이 태그를 만들어서 <form>태그 전송에 포함함

14.5.1 수정/삭제 처리 후 이동

POST 방식으로 진행하는 수정과 삭제 처리는 BoardController에서 각각의 메서드 형태로 구현됨
따라서 페이지 관련 파라미터들을 처리하기 위해서 변경해줌

BoardController의 modify()

    @PostMapping("/modify")
    public String modify(BoardVO board, @ModelAttribute("cri") Criteria cri, RedirectAttributes rttr) {

        log.info("BoardController modify:" + board);

        if (service.modify(board)) {
            rttr.addFlashAttribute("result", "success");
        }

        rttr.addAttribute("pageNum", cri.getPageNum());
        rttr.addAttribute("amount", cri.getAmount());

        return "redirect:/board/list";
    }

메서드의 파라미터에는 Criteria가 추가된 형태로 변경,
RedirectAttribute(rttr)역시 URL 뒤에 원래의 페이지로 이동하기 위해서 pageNum과 amount 값을 가지고 이동하게 수정함

삭제처리 역시 동일하게 Criteria를 받아드리는 방식으로 수정함

BoardController의 remove()

    @PostMapping("/remove")
    public String remove(@RequestParam("bno") Long bno, @ModelAttribute("cri") Criteria cri, RedirectAttributes rttr) {

        log.info("BoardController: remove..." + bno);

        if (service.remove(bno)) {
            rttr.addFlashAttribute("reult", "success");
        }

        rttr.addAttribute("pageNum", cri.getPageNum());
        rttr.addAttribute("amount", cri.getAmount());
        
        return "redirect:/board/list";
    }

위와 같은 방식을 이용하면 수정/삭제 후 기존 사용자가 보던 페이지로 이동하는 것을 볼 수 있음

수정과 달리 삭제는 처리 후 1페이지로 이동해도 무방하지만,
이왕이면 사용자들에게 자신이 보던 정보를 이어서 볼 수 있게 조치해줌

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

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

modify.jsp의 JavaScript 부분

<script type="text/javascript">
	$(document).ready(function() {

		var formObj = $("form");

		$('button').on("click", function(e) {

			e.preventDefault();

			var operation = $(this).data("oper");

			console.log(operation);

			if (operation === 'remove') {
				formObj.attr("action", "/board/remove");
			} else if (operation === 'list') {
				// move list
				formObj.attr("action", "/board/list").attr("method", "get");
			    var pageNumTag = $("input[name='pageNum']").clone();
			    var amountTag = $("input[name='amount']").clone();

				
				formObj.empty();
				formObj.append(pageNumTag);
				formObj.append(amountTag);
			}

			formObj.submit();

		});
	});
</script>

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


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

페이지 이동의 모든 작업에서 정상적으로 이루어지는걸 확인했다면 최종적으로는 DB에 있는 실제 게시물의 수(total)를
구해서 PageDTO를 구성할 때 전달해 주어야함

전체 개수를 구하는 SQL은 어렵거나 복잡하지 않기 때문에 어노테이션으로 처리해도 무방함,
하지만 일관성 있게 BoardMapper 인터페이스에 getTotalCount()메서드를 정의하고
XML을 이용해서 SQL을 처리함

BoardMapper 인터페이스

package com.crow.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;

import com.crow.domain.BoardVO;
import com.crow.domain.Criteria;

public interface BoardMapper {

    ...생략...
    
    public int getTotalCount(Criteria cri);
}

사실 getTotalCount()는 Criteria 파라미터를 전달받지 않도록 설계해도 문제가 없음,
하지만 게시글의 목록과 전체 테이터 수를 구하는 작업은 일관성 있게 Criteria를 파라미터로 전달 받는 것이 좋음
(검색에서 활용하기 위함)

BoardMapper.xml

    <select id="getTotalCount" resultType="int">
        select count(*) from tbl_board where bno > 0
    </select>

BoardService와 BoardServiceImpl에서는 별도의 메서드를 작성해서 BoardMapper의 getTotalCount를 호출함

BoardService 인터페이스의 일부

package com.crow.service;

import java.util.List;

import com.crow.domain.BoardVO;
import com.crow.domain.Criteria;

public interface BoardService {

//    ... 생략 ...
    
    public int getTotal(Criteria cri);
}

아까도 말했듯이 getTotal메서드에 굳이 Criteria를 파라미터로 전달할 필요는 없음,
하지만 목록과 전체 데이터 개수는 항상 같이 동작하는 경우가 많기 때문에 파라미터를 지정함
BoardSerivceImple 클래스는 getTotal() 메서드를 구현함

BoardServiceImpl 클래스의 일부

    @Override
    public int getTotal(Criteria cri) {
        // TODO Auto-generated method stub
        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));
        //model.addAttribute("pageMaker", new PageDTO(cri, 123));
        
        int total = service.getTotal(cri);
        
        log.info("total: " + total);
        
        model.addAttribute("pageMaker", new PageDTO(cri, total));
    }
⚠️ **GitHub.com Fallback** ⚠️