스프링 프레임워크/스프링 MVC

# Spring MVC 게시판 예제 21 : 게시글 페이징

edenDev 2023. 3. 24.

본 포스팅의 예제는 STS 또는 Eclipse를 사용하지 않고 Intellij를 통해 구현하고 있습니다.

그래서 기존의 STS(Spring Tool Studio)에서 생성된 Spring 프로젝트의 스프링 관련 설정 파일명과

프로젝트 구조가 약간 다를 수 있습니다.Intellij 스프링 mvc 프로젝트 생성 포스팅을 참고해주시면 감사하겠습니다.


Spring-MVC 기본 개념 및 테스트 예제 관련 포스팅 링크

순서 포스팅 제목
1 Intellij에서 Spring MVC Project 생성하기
2 Spring MVC - MariaDB 연결테스트
3 Spring MVC - Mybatis 설정 및 테스트
4 SpringMVC 구조
5 SpringMVC + Mybatis
6 Spring MVC Controller 작성 연습
7 Spring Interceptor

 

Spring-MVC 게시판 예제 이전 포스팅 링크

순서 포스팅제목
1 IntelliJ를 이용한 Spring MVC Project 생성 하기
2 Bootstrap AdminLTE Template 적용하기
3 ExceptionResovler : 예외페이지 처리
4 Spring AOP 적용하기
5 회원가입 구현하기
6 회원 유효성 검사 (Feat. JavaScript)
7 회원 유효성 검사 (Feat. Validation)
8 HttpSession을 이용한 로그인 구현하기
9 아이디 찾기 및 패스워드 찾기
10 내 프로필 페이지 및 회원정보 변경 및 탈퇴 구현
11 인터셉터 권한 체크
12 계정 복구 페이지 구현
13 자동로그인
14 게시글 목록 출력
15 게시글 작성
16 게시글 조회수 증가 및 중복 증가 방지
17 게시글 수정 및 삭제
18 게시글 좋아요
19 게시글 북마크
20 게시글 검색

 


1. 페이징(Paging)의 필요성

 

현재까지 구현된 게시판 리스트는 전체 데이터를 출력하고 있습니다. 이 경우 데이터의 양이 많아질 경우 다량의 데이터로 인해 문제가 발생 할 수 있습니다. 데이터의 양이 많아질 수록 한페이지를 불러오는데 필요 한 시간이 길어지게 될 것이고 웹 브라우저 실행 시 메모리 문제가 발생할 가능성도 있는 것입니다. 그렇기 때문에 페이징 처리를 해줌으로써 필요한 데이터만 선택적으로 가져오는 방식을 사용하는 것입니다. 데이터의 양이 많더라도 필요로 하는 양의 데이터만 불러오게 되면서 전체 데이터의 양에 상관없이 일정한 로딩 속도를 가질 수 있게 되는 것입니다.

 


2. 페이징 (Paging) 처리

페이징 처리를 구현함에 있어서 미리 알아두어야 하는 점으로는 페이징 처리시 반드시 필요한 페이지 번호만을 제공해야 합니다. 예를 들어 총 51개의 게시물이 존재하고 페이지당 10개의 게시물을 보여주기로 했다면, 6페이지까지 출력 되어야한다는 것입니다.

 

그리고 페이징 처리를  확인하기 위해서는 DB에 다량의 데이터를 입력해주는 것이 좋습니다. DB를 입력하는 방법으로는 직접 입력도 가능하지만 직접 입력 하는 것은 번거로운 과정이 아닐 수 없기에 Test를 위한 클래스를 작성하여 반복문으로 데이터를 입력 하는 방법을 추천 합니다.

 

페이징 처리를 위해 사용되는 SQL문에서 rownum 키워드를 사용해서 시작데이터와 출력할 데이터의 갯수를 지정 할 수 있습니다.

 

2.1 게시글 페이징 영속 계층 (Persistence Tier) 구현

 

src/기본패키지/board/persistence 패키지 안에 BoardDAO 인터페이스와 BoardDAOImpl 클래스 파일 내용을 아래와 같이 추가 해주세요.

    //  게시글 목록
    public List<BoardVo> getBoardList(int search_category_no, String keyword, int pageNum);

    //  게시글 목록 (카테고리별 정렬)
    public List<BoardVo> getBoardByCategoryList(int category_no, int search_category_no, String keyword, int pageNum);

    //  게시글 총 갯수
    public int getBoardCount(int search_category_no, String keyword);
    //  게시글 목록
    @Override
    @LogException
    public List<BoardVo> getBoardList(int search_category_no, String keyword, int pageNum) {

        HashMap<String, Object> param = new HashMap<>();
        param.put("search_category_no", search_category_no);
        param.put("keyword", keyword);
        param.put("pageNum", pageNum);

        return sqlSession.selectList(NAMESPACE + ".getBoardList", param);
    }

    //  게시글 목록 (카테고리별 정렬)
    @Override
    @LogException
    public List<BoardVo> getBoardByCategoryList(int category_no, int search_category_no, String keyword, int pageNum) {

        HashMap<String, Object> param = new HashMap<>();
        param.put("category_no", category_no);
        param.put("search_category_no", search_category_no);
        param.put("keyword", keyword);
        param.put("pageNum", pageNum);

        return sqlSession.selectList(NAMESPACE + ".getBoardByCategoryList", param);
    }


    //  게시글 총 갯수
    @Override
    @LogException
    public int getBoardCount(int search_category_no, String keyword) {
        HashMap<String, Object> param = new HashMap<String, Object>();
        param.put("search_category_no", search_category_no);
        param.put("keyword", "keyword");

        return sqlSession.selectOne(NAMESPACE + ".getBoardCount", param);
    }

/resources/mappers/board/BoardSQLMapper.xml 파일 안에 아래와 같이 내용을 추가 해주세요.

  <!-- 게시글 목록 -->
  <select id="getBoardList" resultType="BoardVo">

      select t2.* from(select t1.*, rownum as pageNum from (select eb.* from eden_board eb, eden_user eu where
      eb.user_no = eu.user_no
      <if test="search_category_no != 0">
          <choose>
              <!-- 게시글 제목 검색 -->
              <when test="search_category_no == 1">
                  and eb.board_title like '%' || #{keyword} || '%'
              </when>

              <!-- 게시글 내용 검색 -->
              <when test="search_category_no == 2">
                  and eb.board_content like '%' || #{keyword} || '%'
              </when>

              <!-- 게시글 작성자 검색 -->
              <when test="search_category_no == 3">
                  and eu.user_nickname like '%' || #{keyword} || '%'
              </when>

              <!-- 게시글 제목 + 내용 검색 -->
              <when test="search_category_no == 4">
                  and (eb.board_title like '%' || #{keyword} || '%' or eb.board_content like '%' || #{keyword} || '%')
              </when>

              <!-- 게시글 작성자 + 내용 검색 -->
              <when test="search_category_no == 5">
                  and (eu.user_nickname like '%' || #{keyword} || '%' or eb.board_content like '%' || #{keyword} ||
                  '%')
              </when>

              <!-- 게시글 전체 검색 -->
              <when test="search_category_no == 6">
                  and (eb.board_title like '%' || #{keyword} || '%' or eb.board_content like '%' || #{keyword} || '%'
                  or eu.user_nickname like '%' || #{keyword} || '%')
              </when>

          </choose>
      </if>
      ) t1
      ) t2
      <![CDATA[
WHERE t2.pageNum >= (#{pageNum} - 1) * 10 + 1
AND t2.pageNum <= #{pageNum} * 10
]]>

  </select>

  <!-- 게시글 목록 (카테고리별 정렬) -->
  <select id="getBoardByCategoryList" resultType="BoardVo">
      select t2.* from(select t1.*, rownum as pageNum from (select eb.* from eden_board eb, eden_user eu where
      eb.user_no = eu.user_no and eb.category_no = #{category_no}
      <if test="search_category_no != 0">
          <choose>
              <!-- 게시글 제목 검색 -->
              <when test="search_category_no == 1">
                  and eb.board_title like '%' || #{keyword} || '%'
              </when>

              <!-- 게시글 내용 검색 -->
              <when test="search_category_no == 2">
                  and eb.board_content like '%' || #{keyword} || '%'
              </when>

              <!-- 게시글 작성자 검색 -->
              <when test="search_category_no == 3">
                  and eu.user_nickname like '%' || #{keyword} || '%'
              </when>

              <!-- 게시글 제목 + 내용 검색 -->
              <when test="search_category_no == 4">
                  and (eb.board_title like '%' || #{keyword} || '%' or eb.board_content like '%' || #{keyword} || '%')
              </when>

              <!-- 게시글 작성자 + 내용 검색 -->
              <when test="search_category_no == 5">
                  and (eu.user_nickname like '%' || #{keyword} || '%' or eb.board_content like '%' || #{keyword} ||
                  '%')
              </when>

              <!-- 게시글 전체 검색 -->
              <when test="search_category_no == 6">
                  and (eb.board_title like '%' || #{keyword} || '%' or eb.board_content like '%' || #{keyword} || '%'
                  or eu.user_nickname like '%' || #{keyword} || '%')
              </when>

          </choose>
      </if>
      ) t1
      ) t2
      <![CDATA[
WHERE t2.pageNum >= (#{pageNum} - 1) * 10 + 1
AND t2.pageNum <= #{pageNum} * 10
]]>
  </select>
  
      <!-- 게시글 총 갯수 -->
    <select id="getBoardCount" resultType="int">
        select count(*) FROM eden_board eb , eden_user eu
        where eb.user_no = eu.user_no
        <if test="search_category_no != 0">
            <choose>
                <!-- 게시글 제목 검색 -->
                <when test="search_category_no == 1">
                    and eb.board_title like '%' || #{keyword} || '%'
                </when>

                <!-- 게시글 내용 검색 -->
                <when test="search_category_no == 2">
                    and eb.board_content like '%' || #{keyword} || '%'
                </when>

                <!-- 게시글 작성자 검색 -->
                <when test="search_category_no == 3">
                    and eu.user_nickname like '%' || #{keyword} || '%'
                </when>

                <!-- 게시글 제목 + 내용 검색 -->
                <when test="search_category_no == 4">
                    and (eb.board_title like '%' || #{keyword} || '%' or eb.board_content like '%' || #{keyword} || '%')
                </when>

                <!-- 게시글 작성자 + 내용 검색 -->
                <when test="search_category_no == 5">
                    and (eu.user_nickname like '%' || #{keyword} || '%' or eb.board_content like '%' || #{keyword} || '%')
                </when>

                <!-- 게시글 전체 검색 -->
                <when test="search_category_no == 6">
                    and (eb.board_title like '%' || #{keyword} || '%' or eb.board_content like '%' || #{keyword} || '%'
                    or eu.user_nickname like '%' || #{keyword} || '%')
                </when>

            </choose>
        </if>
    </select>

getBoardCount() 로 게시글의 총 갯수를 불러와서 한 페이지 당 10개의 게시글을 불러오도록 구현 하였습니다.

 

2.2 게시글 페이징 비지니스 계층 (Business Tier) 구현

 

src/기본패키지/board/service 패키지 안에 BoardService 인터페이스와 BoardServiceImpl 클래스에 아래와 같이 내용을 추가 해주세요.

    //  게시글 목록
    public ArrayList<HashMap<String, Object>> getBoardList(int category_no, int search_category_no, String keyword, int pageNum);

    //  게시글 카테고리 목록
    public List<CategoryVo> getCategoryList();

    //  게시글 총 갯수
    public int getBoardCount(int search_category_no, String keyword)
    //  게시글 목록

    @Override
    @LogException
    public ArrayList<HashMap<String, Object>> getBoardList(int category_no, int search_category_no, String keyword, int pageNum) {

        ArrayList<HashMap<String, Object>> data = new ArrayList<HashMap<String, Object>>();

        List<BoardVo> boardVoList;

        if (category_no != 0) {
            boardVoList = boardDAO.getBoardByCategoryList(category_no, search_category_no, keyword, pageNum);
        } else {
            boardVoList = boardDAO.getBoardList(search_category_no, keyword, pageNum);
        }
        for (BoardVo boardVo : boardVoList) {
            int userNo = boardVo.getUser_no();
            UserVo userVo = userDAO.getUserByNo(userNo);
            CategoryVo categoryVo = boardDAO.getCategoryByNo(boardVo.getCategory_no());
            int totalLikeCount = boardDAO.getTotalLikeCount(boardVo.getBoard_no());

            HashMap<String, Object> map = new HashMap<String, Object>();
            map.put("boardVo", boardVo);
            map.put("userVo", userVo);
            map.put("categoryVo", categoryVo);
            map.put("totalLikeCount", totalLikeCount);

            data.add(map);
        }
        return data;
    }


    //  게시글 총 갯수
    @Override
    @LogException
    public int getBoardCount(int search_category_no, String keyword) {
        return boardDAO.getBoardCount(search_category_no, keyword);
    }

2.3 게시글 페이징 컨트롤러 구현

 

src/기본패키지/board/controller 패키지 안에 BoardController 클래스 파일 안에 아래와 같이 내용을 추가 해주세요.

//  게시글 목록 페이지
@PostMapping(value = "postingList")
@LogException
public String postingList(@RequestParam(value = "category_no", defaultValue = "0") int category_no, Model model, @RequestParam(value = "search_category_no", defaultValue = "0") int search_category_no, @RequestParam(value = "keyword", defaultValue = "") String keyword, @RequestParam(value = "pageNum", defaultValue = "1") int pageNum) {

    ArrayList<HashMap<String, Object>> dataList = boardService.getBoardList(category_no, search_category_no, keyword, pageNum);
    int count = boardService.getBoardCount(search_category_no, keyword);

    int totalPageCount = (int) Math.ceil(count / 10.0);

    // 1 2 3 4 5 , 6 7 8 9 10
    int startPage = ((pageNum - 1) / 5) * 5 + 1;
    int endPage = ((pageNum - 1) / 5 + 1) * (5);
    if (endPage > totalPageCount) {
        endPage = totalPageCount;
    }

    model.addAttribute("count", count);
    model.addAttribute("startPage", startPage);
    model.addAttribute("endPage", endPage);
    model.addAttribute("currentPage", pageNum);
    model.addAttribute("totalPageCount", totalPageCount);

    model.addAttribute("category_no", category_no);
    model.addAttribute("dataList", dataList);
    model.addAttribute("list", boardService.getBoardSearchCategoryList());
    model.addAttribute("search_category_no", search_category_no);
    model.addAttribute("keyword", keyword);

    return "board/postingList";
}

2.4 게시글 페이징 뷰페이지 구현 (JSP)

 

web-inf/views/board 패키지 안에 postingList.jsp 파일의 내용을 아래와 같이 수정 해주세요.


<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%@ include file="../include/head.jsp" %>
<%@ include file="../include/user_menu.jsp" %>

<html>
<body class="hold-transition skin-green-light sidebar-mini" oncopy="return false" oncut="return false"
      onpaste="return false">
<div class="wrapper">

    <%@ include file="../include/top_menu.jsp" %>

    <%@ include file="../include/left_menu.jsp" %>

    <div class="content-wrapper">
        <section class="content container-fluid">

            <form name="detailsForm" role="form" method="post">
                <input type="hidden" id="boardNo" name="board_no" value="">
            </form>

            <form name="writeForm" role="form" method="post">
                <input type="hidden" id="category" name="category_no" value="">
            </form>

            <form name="listForm" role="form" method="post">
                <input type="hidden" id="BOARD_NO" name="board_no" value="">
                <input type="hidden" id="CATEGORY_NO" name="category_no" value="">
                <input type="hidden" id="pageNum" name="pageNum" value="">
            </form>

            <div class="box-header with-border">

            </div>
            <div class="box-body">
                <table class="table table-bordered">
                    <thead>
                    <tr>
                        <th class="col-xs-1">글 번호</th>
                        <th class="col-xs-1">카테고리</th>
                        <th class="col-xs-2">제목</th>
                        <th class="col-xs-2">작성자</th>
                        <th class="col-xs-2">작성일</th>
                        <th class="col-xs-1">조회수</th>
                        <th class="col-xs-1">좋아요</th>
                    </tr>
                    </thead>
                    <tbody>
                    <c:forEach items="${dataList }" var="data">
                        <tr>
                            <td>${data.boardVo.board_no}</td>
                            <td>${data.categoryVo.category_name}</td>
                            <td><a
                                    href="javascript:goPage(${data.boardVo.board_no});">${data.boardVo.board_title }</a>
                                <span class="badge bg-teal"><i class="fa fa-comment-o"></i> + ${data.totalCommentCount}</span>
                            </td>
                            <td>${data.userVo.user_nickname}</td>
                            <td><fmt:formatDate value="${data.boardVo.board_write_date }"
                                                pattern="yyyy:MM:dd: HH:mm:ss"/></td>
                            <td><span class="badge bg-red">${data.boardVo.board_view_count}</span></td>
                            <td><span class="badge bg-teal">${data.totalLikeCount}</span></td>
                        </tr>
                    </c:forEach>
                    </tbody>
                </table>
            </div>

            <div class="box-footer">
                <form id="searchForm" action="../board/postingList" method="post">
                    <input type="hidden" name="category_no" value="${category_no}">
                    <div class="form-group col-sm-2">
                        <select name="search_category_no" class="form-control" id="categoryList">
                            <c:forEach items="${list}" var="search">
                                <c:choose>
                                    <c:when test="${search_category_no == search.search_category_no}">
                                        <option value="${search.search_category_no}" selected>
                                                ${search.search_type}
                                        </option>
                                    </c:when>
                                    <c:otherwise>
                                        <option value="${search.search_category_no}">
                                                ${search.search_type}
                                        </option>
                                    </c:otherwise>
                                </c:choose>
                            </c:forEach>
                        </select>
                    </div>
                    <div class="form-group col-sm-10">
                        <div class="input-group">
                            <input type="text" class="form-control" name="keyword" id="keywordInput"
                                   value="${keyword}" placeholder="검색어">
                            <span class="input-group-btn">
                                    <button type="submit" class="btn btn-primary btn-flat" id="searchBtn">
                                        <i class="fa fa-search"></i> 검색
                                    </button>
                                </span>
                        </div>
                    </div>
                </form>
            </div>

            <div class="box-footer">
                <div class="col" align="center"><!-- 페이징...UI -->
                    <nav aria-label="Page navigation example">
                        <ul class="pagination">
                            <c:choose>
                                <c:when test="${startPage <= 1}">
                                    <li class="page-item disabled"><a class="page-link" href="#">&lt;</a></li>
                                </c:when>
                                <c:otherwise>
                                    <li class="page-item"><a class="page-link"
                                                             href="javascript:paging('${startPage - 1}', '${category_no}');">&lt;</a>
                                    </li>
                                </c:otherwise>
                            </c:choose>


                            <c:forEach begin="${startPage }" end="${endPage }" var="i">
                                <c:choose>
                                    <c:when test="${i == currentPageNum }">
                                        <li class="page-item active"><a class="page-link"
                                                                        href="javascript:paging('${i}', '${category_no}');">${i}</a>
                                        </li>
                                    </c:when>
                                    <c:otherwise>
                                        <li class="page-item"><a class="page-link"
                                                                 href="javascript:paging('${i}', ${category_no});">${i}</a>
                                        </li>
                                    </c:otherwise>
                                </c:choose>
                            </c:forEach>
                            <c:choose>
                                <c:when test="${endPage >= totalPageCount}">
                                    <li class="page-item disabled"><a class="page-link">&gt;</a></li>
                                </c:when>
                                <c:otherwise>
                                    <li class="page-item"><a class="page-link"
                                                             href="javascript:paging('${endPage + 1}', '${category_no}');">&gt;</a>
                                    </li>
                                </c:otherwise>
                            </c:choose>
                        </ul>
                    </nav>
                </div>
            </div>

            <div class="box-footer">
                <div class="pull-right">
                    <a class="btn btn-success btn-flat" href="javascript:writePosting(${category_no});">
                        <i class="fa fa-pencil"></i> 글쓰기
                    </a>
                </div>
            </div>
        </section>
    </div>

    <%@ include file="../include/footer.jsp" %>

</div>

<%@ include file="../include/plugin_js.jsp" %>
</body>
</html>

2.5 게시글 페이징 자바 스크립트 구현

 

webapp/resources/dist/js/board 패키지 안에 relatedBulletin.js 파일에 아래와 같이 내용을 추가해주세요.

function paging(page, category_no) {
    const formObj = $("form[name='listForm']");
    $("#pageNum").attr("value", page);
    $("#CATEGORY_NO").attr("value", category_no);
    formObj.attr("action", "../board/postingList");
    formObj.attr("method", "post");
    formObj.submit();
}

2.6 게시글 페이징 화면 확인


3. 마치면서

 

이번에는 게시글 페이징을 구현 해보았습니다. 다음에는 댓글을 구현 해보도록 하겠습니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

댓글