ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Camel][Spring] 게시판 만들기 #5-1. 페이징 처리 (Paging)
    Spring/게시판 만들기 2020. 6. 26. 17:36

    [Camel][Spring] 게시판 만들기 #5. 페이징 처리 (Paging)

     

    본 게시판 만들기 프로젝트는 더블에스 Devlog Spring-MVC 를 참조하여 작성했음을 알려드립니다. 또한 개인적인 학습을 목적으로한 포스팅이기 때문에 완벽하지 않을 수 있음을 알려드립니다. 문제점이나 궁금한점은 댓글로 남겨주시면 감사하겠습니다. 프로젝트 생성에 앞서 이번 게시판 만들기 프로젝트는 이클립스를 사용하여 구현하였습니다. 

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

    현재까지 구현한 게시판리스트는 전체 데이터를 출력하고 있습니다. 하지만 이 경우 데이터의 양이 많아질 경우 다량의 데이터로 인해 문제가 발생할 수 있습니다. 데이터의 양이 많아질 수록 한 페이지를 불러오는데 필요한 시간이 길어지게 될 것이고 웹 브라우저 실행 시 메모리 문제가 발생할 가능성도 있는 것입니다. 

     그렇기 때문에 페이징 처리를 해줌으로써 필요한 데이터만 선택적으로 가져오는 방식을 사용하는 것입니다. 데이터의 양이 많더라도 필요로 하는 양의 데이터만 불러오게되면서 전체 데이터의 양에 상관없이 일정한 로딩 속도를 가질 수 있게 되는 것입니다. 

     

    2. 페이징(Paging) 처리

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

     

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

     

     페이징 처리를 위해 사용되는 SQL문에서 LIMIT 키워드를 사용해서 시작데이터와 출력할 데이터의 갯수를 지정할 수 있습니다. 아래의 예시로 보여줄 SQL문은 게시글을 10개씩 출력하고, 첫 번째 페이지를 출력하게하는 SQL문입니다. 

    SELECT
      article_no,
      title,
      content,
      writer,
      regdate,
      viewcnt
    FROM tb_article
    WHERE article_no > 0
    ORDER BY article_no DESC, regdate DESC
    LIMIT 0, 10

     

    2-1. 페이징(Paging) 처리 적용

    페이징 처리를 적용하기 위해 ArticleDAO 인터페이스에 페이징 처리를 위한 추상메소드를 추가해주고, ArticleDAOImpl 클래스에는 앞서 추가한 추상메소드를 구현해 줍니다. 

    List<ArticleVO> listPaging(int page) throws Exception;
    @Override
    public List<ArticleVO> listPaging(int page) throws Exception {
    
        if (page <= 0) {
            page = 1;
        }
    
        page = (page - 1) * 10;
    
        return sqlSession.selectList(NAMESPACE + ".listPaging", page);
    }

    이 때 오버라이딩한 메소드 내의 if문은 page 값이 음수가 될 수 없도록합니다. 위의 과정을 수행했다면 이제는 articleMapper.xml 파일에 아래와 같은 SQL문을 추가해줍니다.

    <select id="listPaging" resultMap="ArticleResultMap">
        <![CDATA[
        SELECT
            article_no,
            title,
            content,
            writer,
            regdate,
            viewcnt
        FROM tb_article
        WHERE article_no > 0
        ORDER BY article_no DESC, regdate DESC
        LIMIT #{page}, 10
        ]]>
    </select>

      

    2-2. Criteria 클래스 작성

    지금까지의 페이징을 위한 과정을 통해 발생하는 문제점이 있습니다. 첫번째로는 한 페이지에 보여지는 데이터가 10개가 아니라면 LIMIT 구문의 마지막에 입력된 10이라는 수를 변경해야한다는 점이며, 두번째로는 ArticleDAOImpl 클래스에서는 매번 원하는 페이지를 처리할 때마다 계산이 필요하다는 점입니다. 

     

    이러한 문제점들을 해결하기 위해서 페이징 처리를 도와줄 Criteria 클래스를 작성하도록 하겠습니다. Criteria 클래스를 작성함으로써 우리는 페이징 처리의 기준이 되는 변수들을 하나의 객체로 처리하게되면서 보다 편하게 개발할 수 있게됩니다. 

     

    src/main/java/기본패키지/commons/paging 패키지를 생성하고, Criteria 클래스를 아래와 같이 작성하도록 하겠습니다. 

    package com.cameldev.mypage.commons.paging;
    
    public class Criteria {
    
        private int page;
        private int perPageNum;
    
        public Criteria() {
            this.page = 1;
            this.perPageNum = 10;
        }
    
        public void setPage(int page) {
    
            if (page <= 0) {
                this.page = 1;
                return;
            }
    
            this.page = page;
        }
    
        public int getPage() {
            return page;
        }
    
        public void setPerPageNum(int perPageNum) {
    
            if (perPageNum <= 0 || perPageNum > 100) {
                this.perPageNum = 10;
                return;
            }
    
            this.perPageNum = perPageNum;
        }
    
        public int getPerPageNum() {
            return this.perPageNum;
        }
    
        public int getPageStart() {
            return (this.page - 1) * perPageNum;
        }
        
        @Override
        public String toString() {
       	return "Criteria [page=" + page + ", perPageNum="+ perPageNum+"]" ;
        }
    }

    이처럼 Criteria 클래스를 생성했다면, 이제 ArticleDAO 인터페이스와 ArticleDAOImpl 클래스를 수정하도록 하겠습니다. 

    먼저 아래와 같이 추상메소드를 추가하고 오버라이딩 해주도록 하겠습니다. 또한 SQL문 또한 추가해주겠습니다. 

    List<ArticleVO> listCriteria(Criteria criteria) throws Exception;
    @Override
    public List<ArticleVO> listCriteria(Criteria criteria) throws Exception {
        return sqlSession.selectList(NAMESPACE + ".listCriteria", criteria);
    }

    이 때 오버라이딩한 메소드 내의 if문은 page 값이 음수가 될 수 없도록합니다. 위의 과정을 수행했다면 이제는 articleMapper.xml 파일에 아래와 같은 SQL문을 추가해줍니다.

    <select id="listCriteria" resultMap="ArticleResultMap">
        <![CDATA[
        SELECT
            article_no,
            title,
            content,
            writer,
            regdate,
            viewcnt
        FROM tb_article
        WHERE article_no > 0
        ORDER BY article_no DESC, regdate DESC
        LIMIT #{pageStart}, #{perPageNum}
        ]]>
    </select>

      

    이어서 다음으로는 ArticleService 인터페이스와 ArticleServiceImpl 클래스에 아래와 같은 내용을 추가해줍니다. 

    List<ArticleVO> listCriteria(Criteria criteria) throws Exception;
    @Override
    public List<ArticleVO> listCriteria(Criteria criteria) throws Exception {
        return articleDAO.listCriteria(criteria);
    }

     

    이제 마지막으로 컨트롤러에 페이징 목록을 요청할 메소드를 작성해주고 패이징 처리된 목록을 보여주는 list_criteria.jsp파일을 생성해주면됩니다. list_criteria.jsp파일의 내용은 일단 list.jsp파일의 내용을 그래도 복사해서 붙여넣어 줍니다.

    @RequestMapping(value = "/listCriteria", method = RequestMethod.GET)
    public String listCriteria(Model model, Criteria criteria) throws Exception {
        logger.info("listCriteria ...");
        model.addAttribute("articles", articleService.listCriteria(criteria));
        return "/article/list_criteria";
    }

     

    지금까지 구현한 상태는 매번 원하는 페이지로 이동하기 위해서 아래와 같은 형식의 URI를 직접입력해야만합니다.

    http://localhost:8080/mypage/article/listCriteria

    http://localhost:8080/mypage/article/listCriteria?page=2

    http://localhost:8080/mypage/article/listCriteria?page=2&perPageNum=20

     

    이러한 구현은 이상적인 페이징 처리가 아니기 때문에 완벽한 페이징 처리를 위해서는 목록 하단에 페이지 번호를 출력하고 페이지 번호를 클릭하면 해당 페이지로 이동하는 것을 구현해야합니다.  

     

     

    2-3. PageMaker 클래스 작성

    PageMaker 클래스는 앞서 필요했던 목록 하단의 페이지 번호 출력을 위한 클래스입니다. Criteria 클래스와 동일한 디렉터리인 src/main/java/기본패키지/commons/paging 패키지에 PageMaker 클래스를 생성하고 아래와 같이 작성하도록 하겠습니다.

    public class PageMaker {
    
        private int totalCount;
        private int startPage;
        private int endPage;
        private boolean prev;
        private boolean next;
    
        private int displayPageNum = 10; // 하단 페이지 번호의 갯수
    
        private Criteria criteria;
    
        public void setCriteria(Criteria criteria) {
            this.criteria = criteria;
        }
    
        public void setTotalCount(int totalCount) {
            this.totalCount = totalCount;
            calcData();
        }
        
    	// 게시글의 전체 갯수가 결정되면 calcData 메소드를 호출하여 계산 실행
        private void calcData() { 
    
            endPage = (int) (Math.ceil(criteria.getPage() / (double) displayPageNum) * displayPageNum);
    
            startPage = (endPage - displayPageNum) + 1;
    
            int tempEndPage = (int) (Math.ceil(totalCount / (double) criteria.getPerPageNum()));
    
            if (endPage > tempEndPage) {
                endPage = tempEndPage;
            }
    
            prev = startPage == 1 ? false : true;
    
            next = endPage * criteria.getPerPageNum() >= totalCount ? false : true;
    
        }
        
        // Getter Setter는 따로 추가해주시길 바랍니다. 포스팅할 때 코드가 너무 길어져요
    }

     PageMaker 클래스 내의 하단 페이지 번호 출력을 위한 데이터에 대한 설명과 계산 방법에 대한 설명이 필요하신 분들은 더블에스 Devlog Spring MVC Spring-MVC 게시판 예제 07 - 페이징처리를 참고하여 자세하게 확인할 수 있습니다. 

     

    이제 최종적으로 하단 페이지 번호까지 출력하는 이상적인 페이징처리를 하기위해 아래의 코드를 컨트롤러에 추가해주고 페이징 처리된 목록을 보여줄 list_paging.jsp 파일을 생성하겠습니다. list_paging.jsp 파일은 list.jsp 파일을 그래도 복사해서 붙여넣은 뒤 <div class="card-body"></div>태그 다음에 아래의 코드를 추가해줍니다.

    @RequestMapping(value = "/listPaging", method = RequestMethod.GET)
    public String listPaging(Model model, Criteria criteria) throws Exception {
        logger.info("listPaging ...");
    
        PageMaker pageMaker = new PageMaker();
        pageMaker.setCriteria(criteria);
        pageMaker.setTotalCount(1000);
    
        model.addAttribute("articles", articleService.listCriteria(criteria));
        model.addAttribute("pageMaker", pageMaker);
    
        return "/article/list_paging";
    }
    <div class="card-footer">
      <nav aria-label="Contacts Page Navigation">
        <ul class="pagination justify-content-center m-0">
          <c:if test="${pageMaker.prev}">
            <li class="page-item"><a class="page-link"
            href="${path}/article/listPaging?page=${pageMaker.startPage - 1}">이전</a></li>
          </c:if>
          <c:forEach begin="${pageMaker.startPage}"
            end="${pageMaker.endPage}" var="idx">
            <li class="page-item"
            <c:out value="${pageMaker.criteria.page == idx ? 'class=active' : ''}"/>>
            <a class="page-link" href="${path}/article/listPaging?page=${idx}">${idx}</a>
            </li>
          </c:forEach>
          <c:if test="${pageMaker.next && pageMaker.endPage > 0}">
            <li class="page-item"><a class="page-link"
            href="${path}/article/listPaging?page=${pageMaker.endPage + 1}">다음</a></li>
          </c:if>
        </ul>
      </nav>
    </div>

     

    위의 코드를 추가하면 드디어 아래와 같은 결과를 확인할 수 있습니다. 

     

    2-3. 전체 게시글의 수 구하기

    위 결과물을 확인해보면 별 문제가 없어보일 수 있지만 한가지 문제점이 존재합니다. 위의 이미지에서는 알 수 없지만 게시물의 총 갯수는 11개인데 하단 페이지 번호는 10까지 출력되고 있습니다. 

     그 이유는 아직 페이징 처리를 위한 전체 게시글의 수를 구하지 않았기 때문입니다. 그래서 전체 게시글의 수를 구하기 위해 ArticleDAO 인터페이스와 ArticleDAOImpl 클래스에 아래의 코드를 추가하고 articleMapper.xml 파일에도 아래의 코드를 추가해 줍니다. 

    int countArticles(Criteria criteria) throws Exception;
    @Override
    public int countArticles(Criteria criteria) throws Exception {
        return sqlSession.selectOne(NAMESPACE + ".countArticles", criteria);
    }
    <select id="countArticles" resultType="int">
        <![CDATA[
        SELECT
            COUNT(article_no)
        FROM tb_article
        WHERE article_no > 0
        ]]>
    </select>

     

    자연스럽게 ArticleService 인터페이스와 ArticleServiceImpl 클래스에도 코드를 추가해주고 마지막으로 컨트롤러를 아래와 같이 수정해주면 되겠습니다. 

    int countArticles(Criteria criteria) throws Exception;
    @Override
    public int countArticles(Criteria criteria) throws Exception {
        return articleDAO.countArticles(criteria);
    }
    @RequestMapping(value = "/listPaging", method = RequestMethod.GET)
    public String listPaging(Model model, Criteria criteria) throws Exception {
        logger.info("listPaging ...");
    
        PageMaker pageMaker = new PageMaker();
        pageMaker.setCriteria(criteria);
        // 수정
        pageMaker.setTotalCount(articleService.countArticles(criteria));
    
        model.addAttribute("articles", articleService.listCriteria(criteria));
        model.addAttribute("pageMaker", pageMaker);
    
        return "/article/list_paging";
    }

     

    최종적으로 페이징을 구현한 결과 전체 게시물의 갯수가 11개이기 때문에 하단 페이지 목록에 2페이지 까지만 촐력되는 것을 확인 할 수 있습니다. 

     

    이제 페이징 처리를 완료했다면 left_column.jsp에 페이징 처리된 목록을 확인할 수 있는 버튼을 추가해주겠습니다. 

    <aside class="main-sidebar sidebar-dark-primary elevation-4">
        <!-- Brand Logo -->
        <a href="index3.html" class="brand-link">
          <img src="${path}/dist/img/AdminLTELogo.png" alt="AdminLTE Logo" class="brand-image img-circle elevation-3"
               style="opacity: .8">
          <span class="brand-text font-weight-light">AdminLTE 3</span>
        </a>
    
        <!-- Sidebar -->
        <div class="sidebar">
          <!-- Sidebar user panel (optional) -->
          <div class="user-panel mt-3 pb-3 mb-3 d-flex">
            <div class="image">
              <img src="${path}/dist/img/user2-160x160.jpg" class="img-circle elevation-2" alt="User Image">
            </div>
            <div class="info">
              <a href="#" class="d-block">Alexander Pierce</a>
            </div>
          </div>
    
          <!-- Sidebar Menu -->
          <nav class="mt-2">
            <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false">
              <!-- Add icons to the links using the .nav-icon class
                   with font-awesome or any other icon font library -->
              <li class="nav-item has-treeview menu-open">
                <a href="#" class="nav-link">
                  <i class="nav-icon fas fa-tachometer-alt"></i>
                  <p>
                    Starter Pages
                    <i class="right fas fa-angle-left"></i>
                  </p>
                </a>
                <ul class="nav nav-treeview">
                  <li class="nav-item">
                    <a href="${path}/article/write" class="nav-link">
                      <i class="far fa-circle nav-icon"></i>
                      <p>Write Page</p>
                    </a>
                  </li>
                  <li class="nav-item">
                    <a href="${path}/article/list" class="nav-link">
                      <i class="far fa-circle nav-icon"></i>
                      <p>List Page</p>
                    </a>
                  </li>
                   <!-- List Paging 목록 버튼 추가 -->
                  <li class="nav-item">
                    <a href="${path}/article/listPaging" class="nav-link">
                      <i class="far fa-circle nav-icon"></i>
                      <p>List Paging Page</p>
                    </a>
                  </li>
                   <!-- ------------------------- -->
                </ul>
              </li>
              <li class="nav-item">
                <a href="#" class="nav-link">
                  <i class="nav-icon fas fa-th"></i>
                  <p>
                    Simple Link
                    <span class="right badge badge-danger">New</span>
                  </p>
                </a>
              </li>
            </ul>
          </nav>
          <!-- /.sidebar-menu -->
        </div>
        <!-- /.sidebar -->
      </aside>

     

     

     

    3. 포스팅을 마치며

    이번 포스팅에서는 다소 내용이 길었지만 게시판 목록 페이징 처리를 구현해보았습니다. 작은 규모의 사이트에서는 필요성이 크지 않을 수 있지만, 규모가 커질수록 데이터의 양이 기하급수적으로 늘어날 수 있기 때문에 페이징 처리를 해주는 것이 중요해 질 수 있습니다. 다음 포스팅에서는 이번 포스팅에서 페이징 처리 구현을 완료했지만 아쉬운 점은 위의 사진과 같이 GET파라미터를 page만 처리했기 때문에 perPageNum이나 그 이상의 정보를 전달할 수 없다는 점이다. 그래서 다음 포스팅에서 페이징 처리를 개선하고, uri를 좀더 나은 방식으로 제어할 수 있는 방법들에 대해 포스팅하겠습니다.

     

    다음포스팅

    2020/06/26 - [Spring/게시판 만들기] - [Camel][Spring] 게시판 만들기 #5-2. 페이징 처리 (Paging) 추가 사항

    글을 읽으시면서 잘못된 부분이나 궁금하신 사항은 댓글 달아주시면 빠른 시일내에 수정 및 답변하도록 하겠습니다. 

    댓글

Camel`s Tistory.