9/08/2019

스프링 게시판 만들기 #9. 페이징 구현 1

지금은 게시물이 몇개 없어서 특별한 문제는 없어(?) 보입니다. 하지만 게시물이 수십, 수백개 이상이 되면 특정 게시물을 찾기 위해 스크롤을 열심히 굴려야하는 불편함이 생깁니다.

이 쿼리는 현재 테이블에 담겨있는 데이터를 그대로 재입력하는 쿼리입니다. 보이는 그대로, 인서트 셀렉트문이라고 합니다.

insert into tbl_board(title, content, writer)
  select title, content, writer from tbl_board

현제 테이블의 데이터를 모두 재입력하기 때문에, 몇번 반복하다보면

이렇게 데이터가 많아집니다. 많은 더미용 데이터가 필요할 때 사용합니다.

게시물의 양이 많아져서, 스크롤도 엄청나게 길어졌습니다.

먼저, 최근 순서대로 출력할 수 있도록 order by 를 이용하여 쿼리를 작성하고 실행해봅니다.

select 
  bno, title, writer, regDate, viewCnt
from tbl_board
order by bno desc

limit 10 을 추가하여, 위에서 작성한 쿼리의 결과에서 10개만 출력할 수 있도록 합니다.

select 
  bno, title, writer, regDate, viewCnt
from tbl_board
order by bno desc
limit 10

limit 0, 10 으로 수정하면, 결과중 0번째를 시작으로 10개가 출력됩니다.

select 
  bno, title, writer, regDate, viewCnt
from tbl_board
order by bno desc
limit 0, 10

첫페이지는 0번째부터 10개이므로 limit 0, 10
두페이지는 11번째부터 10개이므로 limit 10, 10
세페이지는 21번째부터 10개이므로 limit 20, 10
이런식으로 사용할 수 있습니다.

이번엔 게시물의 총 갯수를 구할 쿼리를 작성하고 실행해봅니다.

select count(bno) from tbl_board

게시판의 기준이 되는 컬럼은 게시물 번호(bno)이므로 bno 컬럼을 카운트합니다. 물론 와일드카드 문자(*)를 사용해도 됩니다.

페이징에 필요한 데이터는 다 갖추어졌습니다.

1. 한 페이지에 출력될 게시물 갯수(10개)
2. 페이징에 출력할 번호를 계산하기 위한 게시물의 총 갯수

넵, 사실 페이징 구현은 별거 없습니다.

게시물 총 갯수를 구할 쿼리를 매퍼에 추가합니다.

<!-- 게시물 총 갯수 -->
<select id="count" resultType="int">
 select count(bno) from tbl_board
</select>

DAO와 DAOImpl에 코드를 추가합니다.

// 게시물 총 갯수
public int count() throws Exception;
// 게시물 총 갯수
@Override
public int count() throws Exception {
 return sql.selectOne(namespace + ".count"); 
}

마찬가지로 Service와 ServiceImpl에 코드를 추가합니다.

// 게시물 총 갯수
public int count() throws Exception;
// 게시물 총 갯수
@Override
public int count() throws Exception {
 return dao.count();
}

'게시물 목록' 메서드를 복사/붙여넣기하고, 메서드명을 listPage로 변경합니다.

// 게시물 목록 + 페이징 추가
@RequestMapping(value = "/listpage", method = RequestMethod.GET)
public void getListPage(Model model) throws Exception {
  
 List list = null; 
 list = service.list();
 model.addAttribute("list", list);   
}

다음은 게시물을 10개씩 출력하는 쿼리를 매퍼에 추가합니다.

<!-- 게시물 목록 + 페이징 -->
<select id="listPage" parameterType="hashMap" resultType="com.board.domain.BoardVO">
 select
  bno, title, writer, regDate, viewCnt
 from tbl_board
 order by bno desc
  limit #{displayPost}, #{postNum}
</select>

이때 파라메터타입(parameterType)이 해시맵(hashMap)인것에 주의합니다.

DAO와 DAOImpl에 코드를 추가합니다.

// 게시물 목록 + 페이징
public List listPage(int displayPost, int postNum) throws Exception;
// 게시물 목록 + 페이징
@Override
public List listPage(int displayPost, int postNum) throws Exception {

 HashMap data = new HashMap();
  
 data.put("displayPost", displayPost);
 data.put("postNum", postNum);
  
 return sql.selectList(namespace + ".listPage", data);
}

매개변수인 displayPost,postNum를 해시맵을 이용하여 하나로 그룹지어준 뒤 매퍼에 전송합니다.

DAO와 매퍼에서는 데이터를 하나만 전송할 수 있기 때문에, 2개 이상의 데이터를 다룰 때는 VO(Value Object)를 사용하거나 해시맵을 이용합니다.

Service와 ServiceImpl에 코드를 추가합니다.

// 게시물 목록 + 페이징
public List listPage(int displayPost, int postNum) throws Exception;
// 게시물 목록 + 페이징
@Override
public List listPage(int displayPost, int postNum) throws Exception {
 return dao.listPage(displayPost, postNum);
}

컨트롤러의 listPage 메서드에 코드를 추가합니다.

// 게시물 목록 + 페이징 추가
@RequestMapping(value = "/listPage", method = RequestMethod.GET)
public void getListPage(Model model, @RequestParam("num") int num) throws Exception {
 
 // 게시물 총 갯수
 int count = service.count();
  
 // 한 페이지에 출력할 게시물 갯수
 int postNum = 10;
  
 // 하단 페이징 번호 ([ 게시물 총 갯수 ÷ 한 페이지에 출력할 갯수 ]의 올림)
 int pageNum = (int)Math.ceil((double)count/postNum);
  
 // 출력할 게시물
 int displayPost = (num - 1) * postNum;
    
 List list = null; 
 list = service.listPage(displayPost, postNum);
 model.addAttribute("list", list);   
 model.addAttribute("pageNum", pageNum);
}

매개변수로 num은 페이지 번호입니다.

1. 게시물의 총 갯수를 구하고
2. 한 페이지당 출력할 게시물 갯수를 정하고(10개)
3. 하단에 표시할 페이징 번호의 갯수를 구하고(소수점은 올림)
4. 현재 페이지를 기준으로 10개의 데이터를 출력합니다

하지만 지금은 listPage에 매칭되는 jsp파일이 없으니 생성해야합니다.

list.jsp를 복사/붙여넣기한 뒤 이름을 listPage.jsp로 변경합니다. 사용하는 코드는 거의 일치하므로 이렇게 하는게 작업량이 적습니다.

테이블(table)이 끝나는 다음줄에 페이징 번호를 출력할 코드를 작성합니다.

<div>
 <c:forEach begin="1" end="${pageNum}" var="num">
    <span>
     <a href="/board/listPage?num=${num}">${num}</a>
  </span>
 </c:forEach>
</div>

지금 게시물의 갯수가 1만개를 넘어가기 때문에, 아무런 작업 없이 이렇게 작성하면 1페이지부터 마지막 페이지까지 모두 출력될것입니다.

그러므로 특정 갯수만큼의 페이징 번호를 표시하거나, 중간을 생략하는 방법을 사용해야합니다. 하지만 이 작업은 이후에 하기로하고, 지금은 페이징 자체만을 구현하도록 합니다.

메뉴를 담당하는 nav.jsp파일에 페이징 기능이 구현된 페이지로 이동할 수 있도록 링크를 추가합니다.

<li>
 <a href="/board/listPage?num=1">글 목록(페이징)</a> 
</li>

페이징이 구현된 listPage에 접속하면, 게시물이 10개씩 나오며 하단에 페이징 번호가 출력되는걸 확인할 수 있습니다.

현재 게시물의 갯수가 1만개를 넘어가며 이에 대한 별다른 작업이 없으므로, 페이징 번호가 처음부터 끝까지 모두 출력되고 있습니다. 이 문제에 대해서는 위에서 말한대로 이후에 작업하겠습니다.

수많은(...) 페이징 번호를 중 하나를 클릭해서 다른 페이지로 넘어가보면, 페이지에 맞는 게시물에 출력되는걸 확인할 수 있습니다.

게시물 수정
  1. 정말 도움되는 글입니다! 강력 추천하고 갑니다!

    답글삭제
  2. 익명4/15/2020

    감사합니다 재미있게 따라하고있습니다

    다름이 아니라 이전게시물에서 안보이던것이 추가가 된것같아서 물어보려고합니다.

    nav는 어디에 만들면되는건가요??

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      늦게 확인해서 죄송합니다..ㅠㅠ

      nav는 확인해보니 누락되어서(...) 게시물을 중간에 추가하였습니다.
      #7-5. 메뉴 인클루드 (누락본)

      삭제
    2. 익명4/21/2020

      괜찮습니다 구버전에서 보고 따라만들었습니다 ㅎㅎ

      좋은자료감사합니다

      삭제
  3. 오라클 버전 페이징도 만들어주실순 없나요 ㅠㅠ

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      오라클 버전은 쿼리문만 수정하면 되는데, 일단 구버전 게시물에 있으니 참고바랍니다.

      삭제
  4. 익명9/06/2020

    안녕하세요! 글 잘보고있습니다^^ 글을 보면서 페이징 처리를 하고있는데 혹시 NullPointerException 오류가 나는건 해결법이 없을까요??ㅠㅠ 거의 그대로 한것같은데 이 오류 때문에 페이징 처리를 못하고있습니다 ㅜㅜ 혹시 해결법 있으시다면 답변 부탁드립니다!! 그리고 listPage는 그냥 이름만 바꾸면 되는것인가요?

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      해당 에러는 null 데이터 때문에 생기는 에러입니다.
      에러가 생기는 부분의 코드를 확인하여 어디에서 null이 생기는지 확인해보셔야할것 같습니다.

      삭제
  5. count 메소드는 전체 페이지숫자 갯수고
    그 안에서 1번째부터 n번째 까지는 listpage 로군요.

    답글삭제
  6. List list = null; << 이 부분을 설명한번 해주실수 있나요?? 제네릭 타입인건 알겟는데 검색해서 찾아봐도
    list = service.list(); 이해가 잘 안가서요 ㅠㅠ
    model.addAttribute("list", list);

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      답변이 늦었네요;

      List list = null; 는 List 데이터 타입의 변수 list를 선언하고 null로 초기화
      list = service.list(); 는 선언한 list에 service.list()를 대입하는것입니다.

      여기서 service.list()가 뭐냐하면
      service는 BoardService를 가르키고 (본문에 있습니다)
      .list()는 BoardService 클래스 하위의 list 메서드를 가르킵니다.

      삭제
    2. 알려주셔서 감사합니다 !!

      삭제
  7. 요번 강의가 좀 빡세긴하지만 잘돌아가는거 보니 뿌듯합니다

    답글삭제
  8. 익명7/16/2021

    따라하고 있는데 너무 쉽게 잘 작성하셨는데요
    책보다 쉬워요.. 최고

    답글삭제
  9. Controller getListPage 에서 displayPost 와 postNum가 바뀐것 같아요

    답글삭제
  10. 익명4/18/2023

    오라클 12버전 이상은

    SELECT *
    FROM tbl_board
    ORDER BY bno DESC
    OFFSET #{displayPost} ROWS FETCH NEXT #{postNum} ROWS ONLY

    이 쿼리문 사용하면 되네요. 감사합니다

    답글삭제
  11. 익명8/22/2023

    선생님 복 많이 받으십시오!!

    답글삭제