본문 바로가기
개발입문/SPRING 게시판 만들기

[SPRING] 페이징 처리

by 양히◡̈ 2022. 10. 13.

페이징 조건 클래스 생성

목록 페이지에 행이 많아지면 무한스크롤이 돌게 된다. 그래서 페이지를 나누어 표시되도록 처리할 것이다.

페이징처리에는 여러 조건들이 필요하기 때문에 조건을 넣을 클래스를 만들어 처리한다.

kr.icia.domain > new Class 생성 > name: Criteria

package kr.icia.domain;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class Criteria {
	private int pageNum; //현재 페이지 번호
	private int amount;	//페이지당 게시물 수
	
	public Criteria() {
		this(1,10); 	//아래쪽 전달값 2개 생성자 호출
	}
	
	public Criteria(int pageNum, int amount) {
		this.pageNum = pageNum;
		this.amount = amount;
	}
}

 

BoardMapper.xml 파일을 연다.

<mapper>태그 내에 있는 마지막 <update> 코드 아래에 다음 코드를 작성한다.

<select id="getListWithPaging" resultType="kr.icia.domain.BoardVO">
<![CDATA[select bno, title, content, writer, regdate, updatedate
from
(
select /*+INDEX_DESC(tbl_board pk_board) */ 
rownum rn, bno, title, content, writer, regdate, updatedate 
from 
tbl_board
where rownum <= #{pageNum} * #{amount}
)
where rn > (#{pageNum}-1) * #{amount}]]>
</select>

중간에 /* */ 부분은 인덱스 힌트 부분이다. 

Reference ▶ https://gent.tistory.com/306

 

 

 

이 부분을 호출할 인터페이스도 만들어 주어야 한다.

kr.icia.mapper > BoardMapper.java 를 열고 아래에 추가한다.

public List<BoardVO> getListWithPaging(Criteria cri);

 

예제 실습을 위해 sql developer의 admin 계정으로 접속하여 아래 쿼리문을 반복 실행하며 행을 삽입한다.

insert into tbl_board(bno,title, content,writer,
regdate, updatedate)
select seq_board.nextval, title, content,writer,
regdate, sysdate from tbl_board;

행 추가 후 커밋한다.

 

이제 추가된 getListWithPaiging 메소드를 테스트해볼 것이다.

src/test/java > kr.icia.mapper > BoardMapperTests 를 실행한다.

페이지 처리가 잘 되는지를 확인해보기 위해 1페이지를 보여주도록 pageNum을 1로 설정하고, 한 페이지당 10개의 게시물을 보여주도록 amount는 10으로 설정한다.

 

@Test
public void testPaging() {
    Criteria cri = new Criteria();
    cri.setPageNum(1); //디폴트값이 1,10이므로 pageNum, amount둘 다 set 생략 가능함
    cri.setAmount(10);

    List<BoardVO> list = mapper.getListWithPaging(cri);
    list.forEach(board -> log.info(board.getBno()));
}

 

 

JUnit 테스트를 실행하면 1페이지에 10개의 게시물이 콘솔에 보여진다.

 

이제 BoardService를 수정할 것이다.

src/main/java > kr.icia.service > BoardService 

 

기존에 있던 public List<BoardVO> getList(); 는 주석처리하고 아래에 새로 코드를 추가한다.

public List<BoardVO> getList(Criteria cri);

 

 

src/main/java > kr.icia.service > BoardServiceImp 파일 실행

똑같이 기존에 있던 getList() 는 주석처리한 후 아래 코드를 작성한다.

@Override
public List<BoardVO> getList(Criteria cri) {
    log.info("getListWithPaging......" + cri);
    return mapper.getListWithPaging(cri);
}

 

이제 바꿔준 부분을 테스트하기 위해 test 클래스를 수정할 것이다.

src/test/java > kr.icia.service > BoardServiceTests 실행

@Test
public void testGetList2() {
    service.getList(new Criteria(2, 10)).forEach(board -> log.info(board));
}

2페이지에 해당되는 게시글의 정보가 로그에 출력된다.

 

 

컨트롤러 수정

getList() 를 변경했기 때문에 BoardController에서 오류가 날 것이다.

BoardController를 수정해주어야 한다.

src/main/java > kr.icia.controller > BoardController 실행

오류가 나는 부분을 다음과 같이 수정한다.

Criteria cri라는 argument를 추가했고, addAttribute() 내에 있는 getList 안에 cri 를 넣어줬다.

@GetMapping("/list")
public void list(Model model, Criteria cri) {
    log.info("list");
    model.addAttribute("list", service.getList(cri));
    //java 코드에서 생성된 결과를 jsp 페이지로 전달한다

    //컨트롤러 >> 서비스 >> 매퍼 >> mybatis
}

 

src/test/java > kr.icia.controller > BoadControllerTests

@Test
public void testList2() throws Exception {
    log.info(mockMvc.perform(MockMvcRequestBuilders.get("/board/list").param("pageNum", "2").param("amount", "10"))
            .andReturn().getModelAndView().getModelMap());
    // board/list 요청에 대한 처리를 get 방식으로 하고,
    // 그 결과를 andReturn()으로 받아서,
    // * getModelAndView()를 통해 model로 전환 후,
    // 결과를 getModelMap()으로 jsp페이지로 출력한다.
}

 

서버를 구동해 보면, 10개의 리스트만 보여지는 것을 확인할 수 있다.

 

 

 

페이징 화면처리

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

먼저, 페이지 번호를 전달하는 단계부터 진행할 것이다.

페이징 처리를 위한 여러 정보는 클래스를 구성해서 처리해볼 것이다.

kr.icia.domain > new Class 생성 > name: PageDTO

package kr.icia.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;  //Math.ceil은 소수점 올림처리
		//1페이지라고 가정하면 endPage는 10
		this.startPage = this.endPage -9;
		int realEnd = (int) (Math.ceil((total * 1.0) / cri.getAmount()));
		//총 게시물이 20개라고 가정하면 realEnd=2
		//페이지당 보여줄 게시물 수는 10개로 가정한다
		
		if (realEnd < this.endPage) {
			this.endPage = realEnd;
		}
		this.prev = this.startPage > 1;  //앞으로가기
		this.next = this.endPage < realEnd;  //다음으로가기
	}
}

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

 

BoardController를 열어 코드를 수정한다.

 

@GetMapping("/list")
public void list(Model model, Criteria cri) {
    //Criteria cri는 기본 생성자로 1,10을 가지고 있음
    log.info("list");
    model.addAttribute("list", service.getList(cri));
    //java 코드에서 생성된 결과를 jsp 페이지로 전달한다
    model.addAttribute("pageMaker", new PageDTO(cri,190));
    //총 게시물 수를 임의로 190으로 설정함

자바에서는 전달값을 주면서 호출을 해야 했지만, 스프링 컨트롤러에서는 매개변수에 사용하겠다고 명시하면 알아서 만들어 준다.

이전에는 list 만 출력되었는데 이제는 페이지번호(pageDTO)도 출력이 된다.

페이지번호를 웹페이지에 출력하기 위해 list.jsp 를 열어 수정해볼 것이다.

</table> 태그 아래에 입력한다.

<div>
    <ul class="pagination justify-content-center">
        <c:if test="${pageMaker.prev}">
            <li class="page-item"><a href="${pageMaker.startPage-1 }"
                class="page-link">이전</a></li>
        </c:if>

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

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

<c:if>나 <c:forEach>와 같은 문법을 JSTL이라고 한다.

사용된 <c:if>문을 보면 pageMaker.prev(next)값이 true라면 보여주라는 의미이다.

<li class='page-item ${pageMaker.cri.pageNum==num?"active":"" }'>

이 부분은 페이지번호가 일치하면 파란색으로 표시되도록 하는 것이다.

​Reference ▶ https://daesuni.github.io/jstl/

 
 
 
 

페이지번호 클릭 이벤트처리

방금 list.jsp에서 작성한 <div> 아래에 <form>을 추가한다.

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

 

이제 스크립트 부분을 수정해줄 것이다.

$(document).ready(function() 안에 새 코드를 추가한다.

var actionForm = $("#actionForm");
$(".page-item a").on("click", function(e) {
    //page-item 클래스의 a링크
    e.preventDefault(); 
    console.log("click");  
    actionForm.find("input[name='pageNum']").val($(this).attr("href"));
    actionForm.submit();
});

서버를 구동해보면 페이지번호가 추가되었고, 다음을 누르면 11~20 페이지를 보여준다.

그러나 아까 최대 게시물을 190개로 임의 지정했으므로 19페이지까지만 표시되는 것을 볼 수 있다.

(추후 수정해야한다.)

 

 

 

글 조회시 현재페이지 정보 넘기기

페이지 이동은 잘 되고 있지만, 특정 게시글을 조회한 뒤 목록으로 돌아왔을 때 페이지가 풀리게 된다.

이는 다시 목록으로 돌아갈 때 현재 페이지의 번호 정보가 넘어오지 않아서 그렇다.

이제 이 부분을 보완할 것이다.

list.jsp 에서 아래 선택된 부분을 지우고 코드를 변경한다.

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

 

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

새로고침한 후 목록에 있는 게시글을 아무거나 눌러보자.

URL에 페이지번호(pageNum)와 페이지당 게시물 수(amount) 정보가 같이 넘어가는 것을 볼 수 있다.

 

 

 

 

 

조회 페이지의 목록 버튼 이벤트 처리

그러면 이제 조회 페이지에서 목록 버튼을 눌렀을 때 이 URL로 돌아갈 수 있도록 구현할 것이다.

/list 에서 /get 으로 넘어갈 때는 pageNum, amount, bno의 값이 필요했지만, 반대로 /get에서 /list로 돌아갈 때는 bno의 값이 빠진 채 넘어가면 그대로 유지가 될 것이다.

src/main/java > kr.icia.controller > BoardController 파일을 수정한다.

이 부분을 수정할 것이다.

기존에는 파라미터로 bno와 model만 들어가 있었지만, 이제 페이지번호와 관련된 정보도 함께 넘어가야 하므로 Criteria 를 추가해준다.

@ModelAttribute 을 사용했는데, 이 어노테이션은 자동으로 Model에 데이터를 지정한 이름으로 담아준다.

// 제목 링크를 클릭하여 글 상세보기 - get 방식
@GetMapping({ "/get", "/modify" })
public void get(@RequestParam("bno") Long bno, @ModelAttribute("cri") Criteria cri, Model model) {
    // @RequestParam: 요청 전달값으로 글번호 이용
    // @ModelAttribute: 자동으로 객체를 할당하여 전달함
    // 자동으로 생성된 Criteria cri를 모델값으로 저장하는데 저장명이 cri이다
    log.info("/get or modify");
    model.addAttribute("board", service.get(bno));
    // 전달값으로 명시만 하면 스프링이 자동처리
    // 사용하는 부분만 추가 구현
}

 

 

get.jsp도 수정한다.

기존 get.jsp는 버튼을 클릭하면 <form>태그를 이용하는 방식이었으므로,

<form> 태그 내부에 input 태그를 2개 더 추가한다.

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

웹페이지를 새로고침 한 후, 페이지를 이동하여 게시글 조회하고 목록버튼을 누르면 /board/list 화면으로 넘어가면서 pageNum과 amount에 대한 정보도 함께 넘어가게 된다.

이러한 원리로 기존 페이지가 유지되는 것이다.

(교재에서는 이 부분에 value를 넣을 때 <c:out>을 이용했다.

JSTL인 <c:out>을 사용하고 안 하고의 차이점이 무엇일까에 대한 내용은 아래 사이트에서 참고했다.)

Reference ▶ [JSP]<c:out>을 사용하는 이유 (tistory.com)

 

 

 

 

게시글을 수정/삭제한 후에 목록으로 돌아왔을 때도 페이지 유지하기

get.jsp를 수정했던 방법과 동일하다.

modify.jsp 를 연다.

 

<form>태그 아래에 hidden값으로 bno <input>태그가 있는데, 바로 아래에 pageNum과 amount도 추가한다.

<form role="form" action="/board/modify" method="post">
    <input type="hidden" name="bno" value="${board.bno }"/>
    <input type="hidden" name="pageNum" value="${cri.pageNum }"/>
    <input type="hidden" name="amount" value="${cri.amount }"/>

 

 

BoardController를 다시 연다.

이 부분에서 modify() 안 인자값으로 board, rttr 사이에 "Criteria cri"도 추가해준다.
그리고 if문이 끝나는 곳 뒤에 addAttribute 함수를 추가한다.

// post요청으로 /modify가 온다면 아래 메소드를 수행
@PostMapping("/modify")
public String modify(BoardVO board, Criteria cri, RedirectAttributes rttr) {
    log.info("modify:" + board);
    if (service.modify(board)) {
        rttr.addFlashAttribute("result", "success");
    } // 수정이 성공하면 success 메세지가 포함되어 이동하고, 실패해도 메세지 빼고 이동함
    rttr.addAttribute("pageNum", cri.getPageNum());
    rttr.addAttribute("amount", cri.getAmount());
    return "redirect:/board/list";
}

 

 

위와 마찬가지로 "/remove"도 수정한다.

@PostMapping("/remove")
public String remove(@RequestParam("bno") Long bno, Criteria cri, RedirectAttributes rttr) {
    log.info("remove..." + bno);
    if (service.remove(bno)) {
        rttr.addFlashAttribute("result", "success");
    }
    rttr.addAttribute("pageNum", cri.getPageNum());
    rttr.addAttribute("amount", cri.getAmount());
    return "redirect:/board/list";
}

 

 

 

다시 modify.jsp와서 <script>부분을 수정한다. (else if문)

        } else if (operation === 'list') {
            formObj.attr("action", "/board/list").attr("method","get");

            var pageNumTag=$("input[name='pageNum']");
            var amountTag=$("input[name='amount']");

            formObj.empty(); //form의 내용 비우기
            formObj.append(pageNumTag);
            formObj.append(amountTag);
        }
        formObj.submit(); /* 위의 조건이 아니라면 수정처리 */
    });
});

사용자가 list 버튼을 클릭하면, <form>태그의 모든 내용을 지우고, 이후에 다시 필요한 태그만 추가해서 /board/list를 호출하는 형태이다.

* jquery.append() : 특정 요소의 마지막 부분에 특성을 추가한다.

.append() | jQuery API Documentation

새로고침 후 게시글을 수정이나 삭제한 후에도 페이지가 유지되는 것을 볼 수 있다.

 

 

 

 

전체 데이터의 개수로 페이징 처리

이전에 페이징 처리를 할 때, 임의로 190의 숫자를 부여해서 190개의 게시글만 표시되도록 만들었다.

이제는 실제 게시글의 개수가 모두 표시되면서 페이징처리가 가능하도록 변경해 주어야 한다.

src/main/java > kr.icia.mapper > BoardMapper.java 를 연다.

아래에 코드를 추가한다.

public int getTotalCount(Criteria cri);

 

src/main/resources > kr > icia > mapper > BoardMapper.xml 을 연다.

<mapper>태그 내부 가장 하단에 추가한다.

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

 

src/main/java > kr.icia.service > BoardServide.java 를 열고, 하단에 코드를 추가한다.

public int getTotal(Criteria cri);

 

src/main/java > kr.icia.service > BoardServideImp.java 를 열고, 하단에 코드를 추가한다.

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

 

 

src/main/java > kr.icia.controller > BoardController 를 연다.

이 부분을 BoardService 인터페이스를 통해서 getTotal()을 호출하도록 수정한다.
기존의 pageMaker 속성을 추가했던 부분은 주석처리하고 추가로 작성한다.

@GetMapping("/list")
public void list(Model model, Criteria cri) {
    // Criteria cri는 기본 생성자로 1,10을 가지고 있음
    log.info("list");
    model.addAttribute("list", service.getList(cri));
    // java 코드에서 생성된 결과를 jsp 페이지로 전달한다
    // 컨트롤러 >> 서비스 >> 매퍼 >> mybatis

    /* model.addAttribute("pageMaker", new PageDTO(cri, 190));
    // 총 게시물 수를 임의로 190으로 설정함 */

    int total = service.getTotal(cri);
    log.info("total");
    model.addAttribute("pageMaker", new PageDTO(cri, total));
}

 

 

서버를 재구동해서 /board/list로 가면 이제 1번 게시물까지 전체 페이지가 모두 표시된다.

 

 

 

댓글