본문 바로가기

Study/Spring & Spring Boot

[Spring Boot] 게시판 기능 구현하기(2)

게시글 리스트에 페이징 기능 추가하기


라이브러리 준비

웹 브라우저는 화면에서 벗어나는 내용을 스크롤을 통해 보여주기 때문에 게시판 내에서 게시글의 리스트가 너무 많으면 사용자가 불편함을 호소한다.

그래서 웹에서는 페이징이라는 개념을 통해 한 페이지 당 보여줄 게시글 리스트를 조절할 수 있다.

페이징 기능을 사용하기 위해 라이브러리가 준비되어야 한다.

https://github.com/josecebe/twbs-pagination

여기에서 jquery.twbsPagination.min.js라는 파일이 필요하며 전체적인 화면단을 보여주는 프로젝트의 구성은 다음과 같다.

front
    |_ index.html
    |_ img
        |_ arrow.png
    |_ auth
        |_ account.html
        |_ login.html
    |_ board
        |_ bbs-answer.html
        |_ bbs-update.html
        |_ board-detail.html
        |_ boardwrite.html
        |_ boards.html
    |_ paging
        |_ jquery.twbsPagination.min.js

페이징을 구현하기 전 고민해야 할 사항

페이징을 하기 위해서는 한 페이지 당 보여줄 게시글의 수를 조절해야 하며, 검색했을 때 리스트에 보이는 글의 개수에 따라 페이지 번호가 갱신되어야 한다.

BbsParam에 변수 추가하기


import lombok.Getter;

@Getter
public class BbsParam {

    private String choice;
    private String search;

    private int page;   // 페이징을 갱신하기 위해 필요함.

    private int start;
    private int end;

    public BbsParam() {
    }

    public BbsParam(String choice, String search, int page, int start, int end) {
        this.choice = choice;
        this.search = search;
        this.page = page;
        this.start = start;
        this.end = end;
    }

    public void setChoice(String choice) {
        this.choice = choice;
    }

    public void setSearch(String search) {
        this.search = search;
    }

    public void setPage(int page) {
        this.page = page;
    }

    public void setStart(int start) {
        this.start = start;
    }

    public void setEnd(int end) {
        this.end = end;
    }
}

Mapper 파일 작성


페이징에 필요한 매퍼 작성하기

글의 총 개수에 따라 페이징이 갱신되어야 하므로 다음과 같이 매퍼를 추가해주었다.

<!-- 검색 결과에 따라 글의 총 개수를 반환해주기 위한 코드 -->
<select id="getBbsListSearchPage"
        parameterType="com.hwangduil.springbootbackend.dto.BbsParam"
        resultType="com.hwangduil.springbootbackend.dto.BbsDto"
        >
  SELECT SEQ, ID, REF, STEP, DEPTH, TITLE, CONTENT, WDATE, DEL, READCOUNT
  FROM
  (SELECT ROW_NUMBER()OVER(ORDER BY REF DESC, STEP ASC) AS RNUM,
  SEQ, ID, REF, STEP, DEPTH, TITLE, CONTENT, WDATE, DEL, READCOUNT FROM BBS
  WHERE 1 = 1
    <if test="choice != null and choice != '' and search != null and search !=''">
      <if test="choice == 'title'">
        AND TITLE LIKE CONCAT('%', #{search}, '%')
      </if>
      <if test="choice == 'content'">
        AND CONTENT LIKE CONCAT('%', #{search}, '%')
      </if>
      <if test="choice == 'writer'">
        AND ID=${search}
      </if>
    </if>
    ORDER BY REF DESC, STEP ASC) AS N
    WHERE RNUM BETWEEN #{start} AND #{end}
</select>

<!-- 글의 총 개수 -->
<select id="getBbsCount" parameterType="com.hwangduil.springbootbackend.dto.BbsParam" resultType="Integer">
  SELECT IFNULL(COUNT(*), 0) AS CNT FROM BBS
  WHERE 1 = 1
    <if test="choice != null and choice != '' and search != null and search !=''">
      <if test="choice == 'title'">
        AND TITLE LIKE CONCAT('%', #{search}, '%')
      </if>
      <if test="choice == 'content'">
        AND CONTENT LIKE CONCAT('%', #{search}, '%')
      </if>
      <if test="choice == 'writer'">
        AND ID=${search}
      </if>
    </if>
</select>

DAO 작성

별도로 주석을 달아준 메서드가 추가된다.

import com.hwangduil.springbootbackend.dto.BbsDto;
import com.hwangduil.springbootbackend.dto.BbsParam;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper
@Repository
public interface BbsDao {

    List<BbsDto> getBbsList();

    void insertBbs(BbsDto dto);

    BbsDto getBbsDetail(int seq);

    void readcount(int seq);

    List<BbsDto> getBbsListSearch(BbsParam param);

    List<BbsDto> getBbsListSearchPage(BbsParam param);    // 추가됨

    int getBbsCount(BbsParam param);    // 추가됨

}

Service 구현

import com.hwangduil.springbootbackend.dao.BbsDao;
import com.hwangduil.springbootbackend.dto.BbsDto;
import com.hwangduil.springbootbackend.dto.BbsParam;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional
@RequiredArgsConstructor
public class BbsService {

    private final BbsDao dao;

    public List<BbsDto> getBbsList() {
        return dao.getBbsList();
    }

    public void insertBbs(BbsDto dto) {
        dao.insertBbs(dto);
    }

    public BbsDto getBbsDetail(int seq) {
        return dao.getBbsDetail(seq);
    }

    public void readcount(int seq) {
        dao.readcount(seq);
    }

    public List<BbsDto> getBbsListSearch(BbsParam param) {
        return dao.getBbsListSearch(param);
    }

    public List<BbsDto> getBbsListSearchPage(BbsParam param) {    // 추가
        return dao.getBbsListSearchPage(param);
    }

    public int getBbsCount(BbsParam param) {    // 추가
        return dao.getBbsCount(param);
    }

}

Controller 작성

import com.hwangduil.springbootbackend.dto.BbsDto;
import com.hwangduil.springbootbackend.dto.BbsParam;
import com.hwangduil.springbootbackend.service.BbsService;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequiredArgsConstructor
public class BbsController {

    public final Logger logger = LoggerFactory.getLogger(BbsController.class);
    private final BbsService service;

    @GetMapping("/getBbsList")
    public List<BbsDto> getBbsList() {
        logger.info("BbsController getBbsList");
        return service.getBbsList();
    }

    @GetMapping("/insertBbs")
    public void insertBbs(BbsDto dto) {
        logger.info("BbsController insertBbs() ");
        service.insertBbs(dto);
    }

    @GetMapping("/getBbsDetail")
    public BbsDto getBbsDetail(int seq) {
        logger.info("BbsController getBbsDetail()");
        service.readcount(seq);
        return service.getBbsDetail(seq);
    }

    @GetMapping("getBbsListSearch")
    public List<BbsDto> getBbsListSearch(BbsParam param) {
        logger.info("BbsController getBbsListSearch");
        return service.getBbsListSearch(param);
    }

    @GetMapping("/getBbsListSearchPage")
    public List<BbsDto> getBbsListSearchPage(BbsParam param) {        // 추가
        logger.info("BbsController getBbsListSearchPage");

        // 페이지 설정
        int sn = param.getPage();   // 현재 페이지
        int start = sn * 10 + 1;
        int end = (sn + 1) * 10;

        param.setStart(start);
        param.setEnd(end);

        return service.getBbsListSearchPage(param);
    }

    @GetMapping("/getBbsCount")
    public int getBbsCount(BbsParam param) {        // 추가
        logger.info("BbsController getBbsCount");
        return service.getBbsCount(param);
    }

}

프론트엔드 구성


검색 결과를 보여주는 코드

// 검색결과를 보여줄 코드
function getBbsList(page) {
  let choice = $("#_choice").val();
  let search = $("#_search").val();
  $("#tbody").text("");

  $.ajax({
    url: "http://localhost:3000/getBbsListSearchPage",
    type: "GET",
    data: { choice: choice, search: search, page: page },
    success: function(list) {
      console.log(list);

      $.each(list, function(index, list) {  // index = 배열의 index
        let str;
        str =   `<tr>
                     <th>${index+1}</th>
                   <td>$<a href="board-detail.html?seq=${list.seq}">${list.title}</a></td>
                   <td>${list.id}</td>
                   <td>${list.seq}</td>
                 </tr>`;
        $("#tbody").append(str);
      })
    },
    error: function() {
      console.log("error");
    }
  })
}

컨트롤러의 getBbsListSearchPage 패턴에 검색구분, 검색어, 페이지 번호를 넘겨주면 페이지 번호에 의해 startend가 세팅된다.

컨트롤러로부터 넘겨받은 값을 테이블 형태로 뿌려준다.

글의 총 수를 취득하기 위한 함수

// 글의 총 수를 취득
function getBbsCount() {

  let choice = $("#_choice").val();
  let search = $("#_search").val();

  $.ajax({
    url: "http://localhost:3000/getBbsCount",
    type: "GET",
    data: { choice: choice, search: search, page: 0 },
    success: function(count) {
      loadPage(count);
    },
    error: function() {
      console.log('getBbsCount error');
    }
  });
}

getBbsCount 패턴의 컨트롤러에 choice, search 그리고 page 값을 0으로 넘겨준다.

컨트롤러에서 넘겨받은 값을 loadPage 함수에 넣어준다.

function loadPage(totalCount) {
  let pageSize = 10;

  let _totalPages = totalCount / 10   // 한 페이지에 보여줄 글 수

  if (totalCount % 10 > 0) {
    _totalPages++;
  }

  $('#pagination').twbsPagination('destroy'); // 페이지 갱신

  $('#pagination').twbsPagination({
    totalPages: _totalPages,
    visiblePages: 10,
    first: '<span sris-hidden="true">«</span>',
    last: '<span sris-hidden="true">»</span>',
    prev: '이전',
    next: '다음',
    initiateStartPageClick:false,       // 자동호출 방지
    onPageClick: function (event, page) {
      // console.log(`page number : ${page}`);
      getBbsList(page - 1);
    }
  });
}

이 함수를 통해 페이지를 로드해준다. 한 페이지에 최대 10개의 글이 보이도록 pageSize로 선언해주었다.

매개변수로 받은 totalCount을 10으로 나눈 값을 _totalPages에 저장해주었다.

10으로 나눈 나머지가 0보다 클 때 _totalPages에 1씩 더해준다.

twbsPagination은 제시된 내용과 같이 사용한다.