안녕하세요. 지금까지는 게시글 목록 페이지에서 전체 게시글 1000개가 있으면 한 페이지 내에 1000개가 다 출력되고 있는데요.

이제 게시글 목록 페이지에서 [이전][1][2][3]....[10][다음] 과 같은 페이지 처리 부분을 넣어 3 을 클릭하면 3페이지 게시글 목록을

보여주고, 또 옆에 select 박스로 한 페이지당 출력할 게시글 수를 선택할 수 있게 만들어 사용자가 현재 페이지당 출력할 게시글

수도 선택할 수 있게끔 만들어 보도록 하겠습니다.


[    1. 페이징 처리 구현    ]

페이징 처리를 할 때 가장 필요한게 무엇일까요? 저는 페이지를 처리하기 위한 VO 클래스가 필요하다 생각합니다. 그래서 가장 먼저 할 작업은


1) Criteria VO class와 PageMaker VO 클래스 두개를 만들겠습니다.

- Criteria 클래스 : 

: - page : 현재 페이지를 나타내는 필드 변수

 - perPageNum : 페이지당 표시할 게시글의 수

  - getPageStart() : limit #{pageStart}, #{perPageNum} 부분에서 pageStart를 위한 부분

두 필드 변수를 담도록 할 것이고 이때 좀 중요한 부분이 있는데 Mybatis Mapper.xml 부분에서 

게시글 목록을 검색할 때, 페이지에 맞는 게시글을 뽑아와야 합니다. 이때

select * from boardTable order by bno desc limit #{pagStart}, #{perPageNum} 

이런식으로 검색을 해야합니다. 해석해보자면 boardTable에서 bno(게시글)을 내림차순으로 정렬해줍니다. 최신글이 가장 

위로 올라와야하기 때문이죠. 그 다음 limit 부분을 보면 limit 시작 출력 행, 몇개씩 으로 ~부터 n개를 출력하겠다는 부분입니다.

이때, Mapper.xml에서 #{ } 이 안에 들어 있는 변수명은 getter를 호출해서 대입받는 것을 아실겁니다. 따라서

Criteria 클래스에선 getPageStart() 메서드가 추가로 반드시 필요합니다.!!!

이때, getPageStart()를 보면 return (page-1)*perPageNum을 볼 수 있을 것인데 

1페이지일 때 -> 0 ~ 9 게시글

2페이지 ->      10 ~ 19 

3페이지 ->      20 ~ 29

등을 적어놓고 생각해보면 왜 저런 식이 나왔는지 알 수 있습니다.



- PageMaker 클래스

- Criteria cri : 위에서 만든 page와 perPageNum을 가지고 있는 객체

- int totalCount : 전체 게시글 수로 Mapper.xml을 통해 DB에서 전체 게시글 수를 가져와야한다.(가장 중요)
why? 전체 게시글 수가 있어야 이를 기반으로 모든 다른 "페이지"들에 대한 계산이 가능하기 때문.
- int startPage : 현재 페이지 기준에서 시작 페이지 : 현재 페이지가 12쪽이면 11쪽이 startPage일 것이고
현재 페이지가 27페이지면 21페이지가 startPage 이겠죠?
- int endPage : 현재 페이지를 기준으로 한 마지막 페이지

[11][12][13].......[20] : 현제 페이지가 13일 때 startPage는 11, endPage는 20

- boolean prev : [이전]으로 이동하는 버튼이 생길 조건이 되는지를 나타내는 boolean 변수입니다.
- boolean next : [다음]으로 이동하는 버튼이 생길 조건이 되는지를 나타내는 boolean 변수입니다.

--> 특히, 이 PageMaker 클래스는 view 단에서 페이징 처리를 하기 위해 필히 사용되어야 하는 아주 중요한 클래스입니다.
한번 잘 작성해 놓으면 가져다 쓰기 좋겠죠?


이때, PageMaker 클래스에서 주의해야할 점은

다른 변수들을 계산되려면 totalCount(전체 게시글 수)가 먼저 세팅되어야 하는 점입니다. 또한, page와 perPageNum을 이용하려면 Criteria도 먼저 세팅이 되어야 합니다.

따라서, PageMaker 객체를 사용할 때에는 setCriteria()와 setTotalCount()를 먼저 호출해서 값을 지정할 수 있도록 해주어야 한다는 점과, setTotalCount()에서 다른 변수들에 대한 계산을 해야한다는 점을 기억해야만 합니다.

아래 코드를 추가합시다.


각 항목들에 대한 계산은 처음 한번 이해한 뒤엔 가져다만 씁시다.. 너무 수학적이야 ㅠㅠ
그냥 상식(?)적으로... endPage는 (무조건올림)(현재 페이지 / 페이지당 보여줄 게시글 수) * 페이지당 보여줄 게시글 수 
startPage 는 마지막 페이지에서 페이지당 보여질 게시글 수를 빼면 되고

중간에 tempEndPage 부분의 경우 endPage가 현재페이지 기준의 마지막 페이지이기 때문에 전체 게시글에 대한 마지막 페이
지를 알고 있어야 boolean next에 대한 처리를 할 수 있겠죠?




2) /resources/mappers/boardMapper.xml 을 작성해봅시다.(Mybatis DB 처리)

페이징 처리를 하기 위한 DB처리는 어떤 것들이 필요할까요? 
1) 게시글 목록을 가지고 올 때, 페이지 마다 출력해야하는 BoardVO를 담은 List를 반환하는 부분(listCriteria)
2) 전체 게시글이 몇개인지를 가져오는 부분(getTotalCount)
이 두개가 필요할 겁니다.

아래 코드를 작성합시다.


위에서 다 설명했던대로 limit 부분이 보일 것입니다. 최신글을 뽑아내기 위해 order by bno desc와 
페이지에 맞는 게시글을 뽑기 위해

limit 시작 행, 몇개 를 이용해서 BoardVO를 담고 있는 List를 반환하도록 했습니다.

혹시나해서 쓰는 것이지만, 이때 <select 안에서 resultType="BoardVO" 이 부분은 본래 풀 페키지 경로를 포함한 클래스
명으로 써야하지만, 저처럼 클래스 명으로만 쓰고 싶은 경우에는

Mybatis 설정시 <typeAlias>에 default 패키지를 지정해야 한다는 점.... 알고 계시겠죠??
혹시 모르신다면... Mybatis를 한번 더 살펴보시거나, 그냥 resultType="패키지.BoardVO" 로 하시면 됩니다.


3) DAO 작업과 Service 작업

이제 익숙해졌으리라 생각합니다. Mapper.xml에 추가했음으로 당연히 DAO와 Service 객체에도 추가해야겠죠.

- BoardDAO 인터페이스
- BoardDAOImpl 클래스
- BoardService 인터페이스
- BoardServiceImpl 클래스
를 작성합시다.

먼저 BoardDAO

여러분, 정말 혹시나!해서... 페이징 처리를 위한 메서드 2개(listCriteria()와 TotalCount()) 만 추가하시는 것 알죠?
나머지 부분들은 게시글 추가,삭제, 수정 등에 대한 메서드에요 여러분 것과 제가 많이 다를 수 있으니
그 부분은 제껄 그대로 추가하시면.... 오류생깁니다...


BoardService


BoardServiceImpl

다 마찬가지에요 페이징 처리 부분만 복사하세요.



4) BoardController에서 listPage() 부분 작업을 해봅시다.

위에서 얘기한대로 컨트롤러의 게시글 목록을 보여주는 listPage() 메서드에서는 다음과 같은 작업을 해주어야 합니다.

@RequestMapping(value = "/listPage", method = RequestMethod.GET) public String listPage(@ModelAttribute("cri") Criteria cri, Model model) throws Exception {                 // 커맨드 객체로 Criteria를 매개변수로 넣어줘, 넘어오는 page와 perPageNum정보를 받습니다.

// 해당 cri 를 이용해서 service->dao->mapper.xml 순으로 접근하면서 DB처리를 해 cri 전달된

// 현재 페이지 정보를 기준으로 BoardVO 객체를 담은 ArrayList가 반환될 것입니다.

List<BoardVO> dto = service.listCriteria(cri);

// 이제 view jsp 페이지에서 페이징 처리를 위해 사용할 PageMaker 객체를 생성하고 PageMaker pageMaker = new PageMaker();

// 여기 부분을 "꼭" 해 주어야 한댔죠? Criteria를 set해주고 setTotalCount() 를 해주어야

// 페이징처리에 필요한 것들이 내부적으로 계산될 수 있도록 작성했다고 했습니다. pageMaker.setCri(cri); Integer totalNum = service.totalCount(); pageMaker.setTotalCount(totalNum);

// /views/board/listPage.jsp 에서 페이징 처리를 하기 위해 PageMaker 객체를 저장해 놓아야 할 것이고

// 당연히 화면에 게시글을 뿌려주기 위해서 꺼내온 dto도 저장을 해 주어야 할 것입니다.(model 객체에) model.addAttribute("pageMaker", pageMaker); model.addAttribute("list", dto); return "board/listPage"; }



5) 이제 /views/board/listPage.jsp 페이지에 대한 작업과 그에 필요한 사전 작업을 해봅시다.


listPage.jsp에서 이제 페이징 처리를 할 것인데, 페이지에 따라 a태그를 이용해서 href로 페이지를 띄우는 요청을 걸어놓겠죠?

그런데, 이러한 작업이 계속 반복될 것입니다. 그러면 이걸 하드코딩하는 것 보단 메서드로 만들어 놓으면 어떨가요?

ex) <a href="/board/listPage?page=3?perPageNum=10>[3]</a> 3쪽을 누르면 이런식으로 호출되도록 링크를 걸겁니다.

그러면 page변수가 넘어가서 Controller의 listPage(Criteria cri) 해 놓은 부분에서 cri의 setPage()와 setPerPageNum()을 호출해 데이터를

넣겠죠? 이러한 부분이 계속 반복된다는 것입니다. 근대 그나마 여기서는 칠만해요... 솔직히

근대 동적 검색을 지원하는 기능까지 붙이잖아요? 변수가 늘어나고? 엄청 짜증나기 시작할거에요... 그래서 미리 만들어 놓는 것이 좋습니다.


이러한걸 Spring은 지원하는대요 그게 

"UriComponent"라는 겁니다.

<a href="/board/listPage?page=3?perPageNum=10>에서 ? 이 부분부터 "쿼리 문자열"이라고 하잖아요?

그 부분을 생성하는 메서드를 만들건대 page라는 정보와 perPageNum 정보가 필요하니까 두 변수를 가지고 있는 Criteria나 PageMaker에서

해주는게 좋겠죠? 여기서는 PageMaker 에서 해봅시다.

PageMaker class에서 makeQuery(int page) 라는 메서드를 만들어서 작성해봅시다.




6) /views/board/listPage.jsp 게시글 목록을 보여주는 페이지에서 작업합니다.


7) listPage.jsp 페이지에 현재 페이지에 따른 게시글을 표시하도록 합시다.

6번에 작성한 코드 바로 윗 부분에 해주어야 [이전][1][2][3]...[10][다음] 윗 부분에 들어갈 게시글 목록부분입니다.

여기서 관심있게 볼 점은 게시글 목록의 경우 제목을 클릭하면, 해당 상세 읽기 페이지로 분기해야합니다.

따라서, href="/board/read${pageMaker.makeQuery(pageMaker.cri.page)}&bno=${boardVO.bno} 부분을 통해

page, perPageNum, bno(클릭했을 때 읽을 게시글 번호) 정보를 함께 넘겨 주어야 합니다.

그외에 boardVO 객체에 있는 게시글 등록 일 같은 정보를 출력하기 위해 jstl의 <fmt:formatDate 태그를 이용한 것을 알 수 

있습니다.

그리고 마지막 부분에 글을 등록할 수 있는 글등록 버튼을 하나 넣었습니다.



8) listPage.jsp에서 글 등록 버튼을 클릭했을 때 글등록 페이지로 이동하도록 JQuery를 이용해보겠습니다.


<script>

// 글 작성처리에서 글 작성 성공시 redirect 직전에 RedirectAttribute 객체의 addFlashAttribute로 저장한 msg 변수

var result = '${msg}'; // 글작성 등록에 성공했을 때 msg 지정해 논 부분


if (result == 'SUCCESS') {

alert("처리가 완료되었습니다.");

}

// 이 부분이 8번에서 핵심 부분입니다.

$(document).ready(function() {

// 글 등록 버튼을 클릭하면...

$("#register").click(function() {

self.location = "/board/register"; // 글 작성 폼 페이지로 이동한다.

});

});

</script>



9) BoardController의 read() 부분을 수정해봅시다.


글 등록처리야 /board/register 를 호출하면서 글 등록 폼으로 잘 이동할 것이고 문제는 글 제목을 클릭했을 때 

읽는 페이지 부분입니다. 우리가 /board/read?page=3&perPageNum=10 이런식으로 전달했기 때문에

이 read 부분에서도 page와 perPageNum을 Criteria를 이용해서 받아야만 합니다.


그리고 "진짜 진짜" 중요한 점은, 이 page정보와 perPageNum 정보를 숨김값(hidden) 으로 수정페이지나 삭제 페이지에 전달

해야만 합니다. 왜그럴까요?

왜냐하면 여러분이 3페이지의 게시글을 보고 있다가 글으로고 제목을 클릭했습니다. 그런데 글을 읽고 목록으로 다시 돌아오거나

삭제 페이지 혹은 수정페이지로 이동을 했다가 목록으로 돌아왔을 때 첫페이지로 돌아와있다면 어떨까요? 불편하겠죠?(그렇다해줘요...)


80페이지까지 넘기며 보고 있었는데 게시글 한번 클릭해 보고 왔더니 1페이지... 최악이잖아요? 다시 넘기기도 귀찮고 그래서

현재 보고 있었던 page 정보는 무조건 계속 다른 곳으로 전달하면서 유지시켜주어야합니다. 그럴려면 어떻게 할까요?

<form ~~>

<input type="hidden" name="page" value="${pageMaker.cri.page}" />

</form>

이런식으로 숨김값으로해서 전달해주는 것이 필요합니다.


그래서 아래 코드에서는 

우선, 1) read메서드에서 page와 perPageNum을 전달받을 수 있도록 커맨드 객체를 추가시켜주고, readPage쪽 jsp 페이지에서

페이지에 대한 정보를 사용할 수 있도록 model 객체에 add해주어야 합니다.

  2) read.jsp 에서 hidden값으로 값이 넘어갈 수 있도록 작업도 들어가야겠내요.


BoardController의 read()



/views/board/read.jsp


 + [ read.jsp 페이지에서 게시글 정보가 표시되는 부분입니다. ]


<div class="box-body">

<div class="form-group">

<label for="exampleInputEmail1">Title</label> <input type="text"

name='title' class="form-control" value="${boardVO.title}"

readonly="readonly">

</div>

<div class="form-group">

<label for="exampleInputPassword1">Content</label>

<textarea class="form-control" name="content" rows="3"

readonly="readonly">${boardVO.content}</textarea>

</div>

<div class="form-group">

<label for="exampleInputEmail1">Writer</label> <input type="text"

name="writer" class="form-control" value="${boardVO.writer}"

readonly="readonly">

</div>

</div>

<!-- /.box-body -->


<div class="box-footer">

<button type="submit" class="btn btn-warning">수정</button>

<button type="submit" class="btn btn-danger">삭제</button>

<button type="submit" class="btn btn-primary">목록으로</button>

</div>


이 부분을 바로 아래 추가하고


read.jsp 페이지에서 목록가기,수정 등 버튼 클릭에 따른 JQuery 처리


히든 값이 있는 form의 속성들을 JQuery로 변경해가며 수정, 삭제, 목록으로 가기 등에 맞춰 변경해 submit해주고 있습니다.




10) BoardController의 remove() 부분


글을 읽다가 해당 글을 삭제하기를 눌렀을 때, 위의 코드까지 진행하면 page와 perPageNum이 전달될 것입니다.
컨트롤러의 remove() 부분에서는 글을 삭제하고 삭제처리가 완료되면 목록보기로 redirect 해주어야 합니다.

이때, 중요한 점은 redirect시에는 model 객체가 전달되지 않는다는 점 알고 계시죠?

따라서, model을 사용하는 것이 아니고, RedirectAttribute 객체를 사용해야합니다. 다시 한번 말하지만 Model 객체는

redirect시에는 전달되지 않습니다.!





11) 마지막, 수정 페이지에 대한 작업


읽기, 삭제 페이지까지의 작업을 다 했음으로 마지막으로 수정에 관한 작업만 남았습니다.

어차피 위에서 한 작업과 마찬가지로 코드만 추가하도록 하겠습니다. 수정페이지도 read 페이지처럼

hidden으로 데이터를 전송하는 부분이 있겠죠?


- BoardController의 modify 메서드 부분



- /views/board/modify.jsp 페이지 작업


<form role="form" method="post" action="/board/modify"> <!-- action이 없음으로 이 페이지 요청될 때의 url과 동일한 url로 요청하되 post방식으로 됨. -->


<div class="box-body">


<div class="form-group">

<label for="exampleInputEmail1">BNO</label> <input type="text"

name='bno' class="form-control" value="${boardVO.bno}"

readonly="readonly"> <!-- 게시글 번호는 수정하지 못하도록 readonly 로 설정 -->

</div>


<div class="form-group">

<label for="exampleInputEmail1">Title</label> <input type="text"

name='title' class="form-control" value="${boardVO.title}"> <!-- 읽어온 게시글 값들을 미리 세팅해 놔야함 -->

</div>

<div class="form-group">

<label for="exampleInputPassword1">Content</label>

<textarea class="form-control" name="content" rows="3">${boardVO.content}</textarea>

</div>

<div class="form-group">

<label for="exampleInputEmail1">Writer</label> 

<input type="text" name="writer" class="form-control" value="${boardVO.writer}">

</div>

<!-- 숨김값으로 보내줘야 할 값들 -->

<input type="hidden" name="page" value="${cri.page }" />

<input type="hidden" name="perPageNum" value="${cri.perPageNum }" />

</div>

<!-- /.box-body -->

</form>



<div class="box-footer">

<button type="submit" class="btn btn-primary">수정</button>

<button type="submit" class="btn btn-warning">취소</button>

</div>


<script>

$(document).ready(function() {


var formObj = $("form[role='form']");


console.log(formObj);

// 취소 클릭시 다시 리스트 목록페이지로 돌아가도록 함

$(".btn-warning").on("click", function() {

// self.location : 현재 페이지를 다른 페이지로 전환할 떄 사용하는 JQuery 메서드

self.location = "/board/listPage?page=${cri.page}&perPageNum=${cri.perPageNum}";

});

// 수정 버튼 클릭시 폼 제출되도록 함

$(".btn-primary").on("click", function() {

formObj.submit();

});


});

</script>






+ Recent posts