본문 바로가기
개발/Spring

[Html + Javascript + Ajax + Paging] 게시판 페이징 처리

by 코딩하는 흰둥이 2024. 10. 16.

이전글

https://greed-yb.tistory.com/209

 

[SpringBoot+IntelliJ+Oracle+Thymeleaf+Paging] 웹 게시판 만들기(1) - 구성

Java 17 Maven Spring Boot 3.0.3 Oracle 11g IntelliJ Ultimate DBeaver 간단한 CRUD 만 구현해 놓았으며 순서대로 만들면서 올리려고 하였으나 시행착오가 생각보다 많이 생겨서 다 만들고서 완성본을 올린다 메서

greed-yb.tistory.com

 

DataTables 는 자체적으로 페이징 처리를 해주고

Thymeleaf 방식으로 페이징 처리는 이전글에서 다뤄보았다.

Pageable 을 다뤄보기 전에 정리 한번 해보려 한다.

 

 

Controller
@Controller
public class WebController {

    @Autowired
    private PageTestService pageTestService;
    
    // 페이징 테스트 게시판
    @GetMapping("/pageTest")
    public String pageTest() throws Exception{
        return "pageTest";
    }

    // 페이징 테스트 게시판 목록 불러오기
    @GetMapping("/pageTestList")
    @ResponseBody
    public PageMaker<PageTestVo> pageTestList(SearchMaker searchMaker) throws Exception{
        return pageTestService.select(searchMaker);
    }
}

 

 

service
public interface PageTestService {
    // 게시글 조회
    PageMaker<PageTestVo> select(SearchMaker searchMaker) throws Exception;
}

 

 

serviceImpl
@Service
public class PageTestServiceImpl implements PageTestService {

    @Autowired
    private PageTestMapper testMapper;

    @Override
    public PageMaker<PageTestVo> select(SearchMaker searchMaker) throws Exception {
        // 글의 총 개수
        int total = testMapper.total(searchMaker);

        // 글 검색
        List<PageTestVo> vo = testMapper.select(searchMaker);

        return new PageMaker<>(searchMaker, total, vo);
    }
}

 

 

mapper.class
@Mapper
public interface PageTestMapper {
    List<PageTestVo> select(SearchMaker searchMaker) throws Exception;
    int total(SearchMaker searchMaker) throws Exception;
}

 

 

mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.practice.mapper.pageTest.PageTestMapper">
    <select id="select" resultType="com.example.practice.vo.pageTest.PageTestVo">
        SELECT *
        FROM (SELECT ROW_NUMBER() OVER(ORDER BY NO DESC) AS NUM,
                         NO,
                         CONTENT
              FROM PAGETEST
        <if test="searchKeyWord != null">
            WHERE CONTENT LIKE '%' || #{searchKeyWord} || '%'
        </if>
              )
        WHERE NUM BETWEEN #{rowStart} AND #{rowEnd}
    </select>
    
    <select id="total" resultType="int">
        SELECT COUNT(NO) FROM PAGETEST
        <if test="searchKeyWord != null">
            WHERE CONTENT LIKE '%' || #{searchKeyWord} || '%'
        </if>
    </select>
</mapper>

DB 는 Oracle 을 사용했다.

DB 에 맞춰서 시작(rowStart)과 끝(rowEnd)을 설정해야한다.

 

 

vo
public class PageTestVo {

    String no;
    String content;
    
    -- getter , setter 구현부  --
}

 

 

SearchMaker - 검색 페이징
public class SearchMaker {
    private int page;               // 현재 페이지
    private int size;               // 페이지 당 글의 게시물 개수
    private String searchKeyWord;   // 검색 키워드
    private String type;            // 검색 조건

    // Mapper 에서 가져올 데이터 시작과 끝
    private int rowStart;
    private int rowEnd;

    // 기본 값
    public SearchMaker(){
        this.page = 1;
        this.size = 10;
    }

    public int getPage() {
        return page;
    }

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

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

    public String getSearchKeyWord() {
        return searchKeyWord;
    }

    public void setSearchKeyWord(String searchKeyWord) {
        this.searchKeyWord = searchKeyWord;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    // 쿼리에서 가져올 데이터 위치
    public int getRowStart() {
        return ((this.page - 1) * this.size) + 1;
    }

    public void setRowStart(int rowStart) {
        this.rowStart = rowStart;
    }

    // 쿼리에서 가져올 데이터 위치
    public int getRowEnd() {
        return (getRowStart() + this.size) - 1;
    }

    public void setRowEnd(int rowEnd) {
        this.rowEnd = rowEnd;
    }
}

 

 

PageMaker - 페이지 바 
public class PageMaker<T> {

    private int totalPage;          // 페이지 총 개수
    private int size;               // 페이지 당 글 개수
    private int currentPage;        // 현재 페이지
    private int startPage;          // 시작 페이지
    private int endPage;            // 끝 페이지
    private boolean prev;           // 이전 페이지
    private boolean next;           // 다음 페이지
    private List<T> result;         // 검색 데이터

    public PageMaker(SearchMaker searchMaker, int totalCount, List<T> vo){
        this.size = searchMaker.getSize();              // 페이지 당 글 개수
        this.currentPage = searchMaker.getPage();       // 현재 페이지

        // 총 페이지 수
        this.totalPage = (int) Math.ceil((double) totalCount / size);

        // 페이지 버튼 bar
        this.endPage = (int) ((Math.ceil(this.currentPage / 10.0)) * 10); // 페이지 10개씩 보여짐
        this.startPage = this.endPage - 9;                                // 페이지 10개씩 보여짐

//        this.endPage = (int) ((Math.ceil(this.currentPage / 5.0)) * 5);   // 페이지 5개씩 보여짐
//        this.startPage = this.endPage - 5;                                // 페이지 5개씩 보여짐


        if(this.endPage > this.totalPage){
            this.endPage = this.totalPage;
        }

        this.prev = this.startPage > 1;
        this.next = this.totalPage > this.endPage;

        this.result = vo;                               // 조회 한 게시글 데이터
    }


  -- getter , setter 구현부--
  
}

 

 

 

html
<div class="card shadow mb-4">
    <div class="card-header py-3"  style="text-align: right">
        <!--                    <button class="btn btn-secondary" id="writeBtn" type="button" onclick="writePageBtn()">글쓰기</button>-->
    </div>
    <div class="card-body">
        <div class="table-responsive">
            <select class="form-select" id="searchSize">
                <option value="10">10개</option>
                <option value="30">30개</option>
                <option value="50">50개</option>
            </select>
            <div class="row">
                <div class="col-sm-6 mb-3 mb-sm-0">
                    <input type="text" class="form-control form-control-user" id="searchKeyWord" placeholder="제목">
                </div>
                <button class="btn btn-primary" type="button" id="pageTestBtn" onclick="searchBtn()">검색</button>
            </div>
            <table class="table table-bordered" id="board_dataTable" style="width: 98%">
                <thead>
                <tr>
                    <th>No</th>
                    <th>제목</th>
                </tr>
                </thead>
                <tbody>
                <!-- 글 목록 -->
                </tbody>
            </table>
            <!-- css 미적용 -->
            <div id="pagingBar"></div>

            <!-- bootstrap css -->
            <!--<div id="pagingBar">-->
            <!--   <ul class="pagination justify-content-center" id="pagingUl"></ul>-->
            <!--</div>-->
        </div>
    </div>
</div>

<script th:src="@{/bootstrap/js/pageTest.js}"></script>

 

 

 

javascript
// 페이지 기본값
let page = 1;
let size;
let searchKeyWord;


/**
 * 보여지는 글 개수 변경 시
 */
$("#searchSize").change(function (){
    page = 1;
    size = $("#searchSize").val();
    searchKeyWord = $("#searchKeyWord").val();

    search();
})

/**
 * BootStrap 페이지 바 생성
 */
// function pageBar(data){
//     if (data.prev) {
//         $('#pagingUl').append(`<li class="page-item"><a href="#" class="page-link" data-page="1">처음</a></li>`);
//         $('#pagingUl').append(`<li class="page-item"><a href="#" class="page-link" data-page="${data.startPage - 1}">이전</a></li>`);
//     }
//
//     for (let i = data.startPage; i <= data.endPage; i++) {
//
//         let active = (i === data.currentPage) ? 'active' : '';
//
//         $('#pagingUl').append(`<li class="page-item ${active}"><a href="#" class="page-link" id="pageNum_${i}" data-page="${i}">${i}</a></li>`);
//     }
//
//     if (data.next) {
//         $('#pagingUl').append(`<li class="page-item"><a href="#" class="page-link" data-page="${data.endPage + 1}">다음</a></li>`);
//         $('#pagingUl').append(`<li class="page-item"><a href="#" class="page-link" data-page="${data.totalPage}">끝</a></li>`);
//     }
// }

/**
 * 기본 페이지 바 생성
 */
function pageBar(data){
    if (data.prev) {
        $('#pagingBar').append(`<a href="#" data-page="1">처음</a>`);
        $('#pagingBar').append(`<a href="#" data-page="${data.startPage - 1}">이전</a>`);
    }

    for (let i = data.startPage; i <= data.endPage; i++) {
        $('#pagingBar').append(`<a href="#" id="pageNum_${i}" data-page="${i}">${i}</a>`);
    }

    if (data.next) {
        $('#pagingBar').append(`<a href="#" data-page="${data.endPage + 1}">다음</a>`);
        $('#pagingBar').append(`<a href="#" data-page="${data.totalPage}">끝</a>`);
    }

    // 클릭한 페이지 번호에 css 색 넣기
    $("#" + "pageNum_"+page).css("color", "red")
}


/**
 * 페이지 이동
 */
$("#pagingBar").on("click", "a", function (){
    page = $(this).data("page");
    search();
})

/**
 * 검색 버튼
 */
function searchBtn(){
    page = 1;
    search();
}


/**
 * 검색
 */
function search(){
    size = $("#searchSize").val();
    searchKeyWord = $("#searchKeyWord").val();

    $.ajax({
        url : "/pageTestList",
        method : "GET",
        data : { "page": page, "size" : size , "searchKeyWord" : searchKeyWord},
        success : function (data){
            // 목록 초기화
            $("#board_dataTable tbody").empty();

            // 페이지 바 초기화
            $('#pagingBar').empty();    // css 미적용
            // $('#pagingUl').empty();  // bootstrap css 적용

            if(data.result != "" ){
                for (let i = 0; i < data.result.length; i++) {
                    $("#board_dataTable").append('<tr><td>'+ data.result[i].no +'</td><td>'+ data.result[i].content +'</td></tr>');
                }

                pageBar(data);
            }else{
                $("#board_dataTable").append('<tr><td colspan="2">데이터가 없습니다.</td></tr>');
            }
        }
    })
}


$(document).ready(function (){
    // 검색
    search();
});

 

 

 

TEST

CSS 미적용

 

 

 

 

 

 

BootStrap CSS 적용

 

 


BootStrap CSS 가 필요하다면 주석만 반대로 적용해 주면 된다.

TEST 를 위한 DB 테이블과 VO 를 생성했기 때문에

본인의 DB에 맞춰서 변경할 것

댓글