본문 바로가기
카테고리 없음

게시판 구현 / 6. 페이징 (페이지별 검색 설정)

by maverick11471 2024. 7. 21.

2024.07.20 - [스프링 프레임워크] - 게시판 구현 / 6. 페이징

 

게시판 구현 / 6. 페이징

페이징 기술 구현에 대해 알아보자. 페이징이란 페이지번호를 인식하여 검색, 게시물 개수 등을 지정할 수 있는 방법이다. [요약] [Creteria 클래스 생성]package com.bit.springboard.dto;public class Criteria

maverick11471.tistory.com

 

이전 게시판 페이징을 구현하다 문제점이 발생하였다.

 

바로 1페이지에서는 검색이 잘 되는데, 2페이지, 3페이지에서는 검색이 안된다는 것이다.

 

검색에 대한 기능은 이전에 구현한 적이 있다.

2024.07.19 - [스프링 프레임워크] - 게시판 구현 / 5. Mybatis 동적쿼리로 검색하기 기능 구현

 

게시판 구현 / 5. Mybatis 동적쿼리로 검색하기 기능 구현

자유게시판에서 Mybatis 동적쿼리를 활용해 검색하기 기능을 구현하려 한다. [요약] - 검색할 수 있는 방법은 2가지다. 그 2가지가 동작할 수 있도록 구현한다. (script 구현)    * 검색란에 검색 키

maverick11471.tistory.com

 

여기에 스크립트에 현재 페이지 넘버를 1로만 바꿔주면 해결된다.

 

[free-list.jsp / 자유게시판]

<script>
    $(() => {
        $("#search-icon").on("click", (e) => {
        // 돋보기 모양 클릭 시 페이지넘버 1로 변경
            $("input[name='pageNum']").val(1);
            $("#search-form").submit();
        });

        $("input[name='searchKeyword']").on("keypress", (e) => {
            if(e.key === 'Enter') {
        		// 엔터 클릭시 페이지넘버 1로 변경
                $("input[name='pageNum']").val(1);
            }
        });

 

 


그런데 공지사항 게시판에는 페이지 넘버가 없다.

 

그래서 공지사항에는 무한스크롤을 만들려고 한다.

 

[noticePageDto 생성]

package com.bit.springboard.dto;

public class NoticePageDto {
    private int endPage;
    private Criteria cri;
    private int total;

    public NoticePageDto(Criteria cri, int total) {
        this.cri = cri;
        this.total = total;

        this.endPage = (int)(Math.ceil((total / 1.0) / cri.getAmount()));
    }

    public int getEndPage() {
        return endPage;
    }

    public void setEndPage(int endPage) {
        this.endPage = endPage;
    }

    public Criteria getCri() {
        return cri;
    }

    public void setCri(Criteria cri) {
        this.cri = cri;
    }

    public int getTotal() {
        return total;
    }

    public void setTotal(int total) {
        this.total = total;
    }

    public String toString() {
        return "NoticePageDto{" +
                "endPage=" + endPage +
                ", cri=" + cri +
                ", total=" + total +
                '}';
    }
}

 

[boardController 수정]

@RequestMapping("/notice-list.do")
public String noticeListView(Model model, @RequestParam Map<String, String> searchMap, Criteria cri) {
    boardService = applicationContext.getBean("noticeServiceImpl", BoardService.class);

	// 개수를 9개로 지정
    cri.setAmount(9);

    model.addAttribute("noticeList", boardService.getBoardList(searchMap, cri));
    model.addAttribute("searchMap", searchMap);

    int total = boardService.getBoardTotalCnt(searchMap);

	// cri와 total을 매개변수로 한 NoticePageDto를 model에 추가
    model.addAttribute("page", new NoticePageDto(cri, total));

    return "board/notice-list";
}

 

 


[기존 searchMap 과 관련됐던 클래스 수정]

1. [NoticeServiceImpl 수정]

@Override
// 기존 searchMap에서 paramMap으로 변경
public List<BoardDto> getBoardList(Map<String, String> paramMap, Criteria cri) {
    cri.setStartNum((cri.getPageNum() - 1) * cri.getAmount());

	// 변수를 하나로 통합
    Map<String, Object> paramMap = new HashMap<>();
    paramMap.put("search", searchMap);
    paramMap.put("cri", cri);

    return noticeDao.getNoticeList(paramMap);
}

 

2. [noticeMapper 수정]

 - paramMap으로 수정하면서 searchMap 관련 밸류값들을 search. 으로 전부 변경

<select id="getNoticeList" parameterType="map" resultType="board">
    SELECT N.ID
         , N.TITLE
         , N.CONTENT
         , N.WRITER_ID
         , M.NICKNAME
         , N.REGDATE
         , N.MODDATE
         , N.CNT
        FROM NOTICE N
        JOIN MEMBER M
          ON N.WRITER_ID = M.ID
        WHERE 1=1
    <if test="search.searchKeyword != null and search.searchKeyword != ''">
        AND (
            N.TITLE LIKE CONCAT('%', #{search.searchKeyword}, '%')
            OR N.CONTENT LIKE CONCAT('%', #{search.searchKeyword}, '%')
            OR M.NICKNAME LIKE CONCAT('%', #{search.searchKeyword}, '%')
        )
    </if>
    LIMIT #{cri.amount} OFFSET #{cri.startNum}
</select>

 

 

3. notice-list 수정

<div class="container mt-3 w-50">
    <form id="search-form" action="/board/notice-list.do" method="post">
    	// model에 있는 page와 cri를 활용해 수정
        <input type="hidden" name="pageNum" value="${page.cri.pageNum}">
        <input type="hidden" name="amount" value="${page.cri.amount}">
        <input type="hidden" name="endPage" value="${page.endPage}">

[noticeServiceImpl 수정]

   @Override
    public int getBoardTotalCnt(Map<String, String> searchMap) {
        return noticeDao.getBoardTotalCnt(searchMap);
    }
}

 

[noticeDao 수정]

public int getBoardTotalCnt(Map<String, String> searchMap) {
    return mybatis.selectOne("NoticeDao.getBoardTotalCnt", searchMap);
}

 

[noticeMapper 수정]

<select id="getBoardTotalCnt" parameterType="map" resultType="int">
    SELECT COUNT(*)
        FROM NOTICE N
        JOIN MEMBER M
          ON N.WRITER_ID = M.ID
        WHERE 1=1
        <if test="searchKeyword != null and searchKeyword != ''">
            AND (
                N.TITLE LIKE CONCAT('%', #{searchKeyword}, '%')
                OR N.CONTENT LIKE CONCAT('%', #{searchKeyword}, '%')
                OR M.NICKNAME LIKE CONCAT('%', #{searchKeyword}, '%')
            )
        </if>
</select>

 

[notice-list.jsp 수정] - script

 - 무한 스크롤 만들기

 - noticeList는 결과값으로 boardDto를 가져온다.

<script>

// 이 함수는 입력된 date 값이 10보다 작은 경우, 앞에 0을 붙여 두 자리 형식으로 변환합니다. 
// 예를 들어, 3이라는 숫자를 입력하면 03이 반환되고, 12는 그대로 12로 반환됩니다.
const zeroDate = (date) => {
	return date < 10 ? `0\${date}` : date;
}
            
$(window).on("scroll", (e) => {
    // 현재 스크롤의 위치
    // 현재 브라우저 창에서 스크롤된 수직 위치를 가져옵니다. 
    // 즉, 페이지의 최상단에서 현재 스크롤 위치까지의 거리를 픽셀 단위로 반환합니다. 
    // 이 값은 페이지가 얼마나 아래로 스크롤되었는지를 나타냅니다.
    const scrollTop = $(window).scrollTop();
    // 브라우저의 세로길이(스크롤 길이는 포함되지 않음)
    // 현재 브라우저 창의 세로 길이를 픽셀 단위로 가져옵니다. 
    // 이 값은 사용자가 현재 볼 수 있는 화면의 높이를 나타내며, 스크롤 위치와는 관계없이 항상 일정한 값입니다.
    const windowHeight = window.innerHeight;
    // 웹 문서의 세로 길이(스크롤 길이 포함됨)
    // 전체 웹 문서의 세로 길이를 가져옵니다. 
    // 이 값은 페이지의 모든 콘텐츠를 포함한 높이로, 스크롤이 가능한 전체 영역의 높이를 의미합니다.
    const documentHeight = document.documentElement.scrollHeight;

    // 스크롤이 바닥에 닿았는지 여부
    const isBottom = documentHeight <= scrollTop + windowHeight;

    /*console.log(`scrollTop: \${scrollTop}`);
    console.log(`windowHeight: \${windowHeight}`);
    console.log(`documentHeight: \${documentHeight}`);
    console.log(`isBottom: \${isBottom}`);*/

    if(isBottom) {
        // 현재 페이지의 번호가 마지막 페이지의 번호와 같으면 함수 종료
        if($("input[name='pageNum']").val() >= $("input[name='endPage']").val()) {
            return;
        } else {
            // 스크롤이 바닥에 닿으면 현재 페이지 번호 + 1
            $("input[name='pageNum']").val(parseInt($("input[name='pageNum']").val()) + 1);

			// AJAX 요청을 통해 공지사항 목록을 서버에서 받아오고, 
            // 이를 동적으로 HTML 카드 형식으로 생성하여 웹 페이지에 표시하는 기능을 수행
            $.ajax({
                url: '/board/notice-list-ajax.do',
                type: 'post',
                data: $("#search-form").serialize(),
                success: (obj) => {
                    console.log(obj);
                    let htmlStr = "";
                    for(let i = 0; i < obj.noticeList.length; i++) {
                        htmlStr += `
                            <div class="card" style="width: 18rem;">
                                
                                <div class="card-body">
                                    <h5 class="card-title">\${obj.noticeList[i].title}</h5>
                                    <p class="card-text">
                                    작성일: \${obj.noticeList[i].regdate[0]}
                                    -\${zeroDate(obj.noticeList[i].regdate[1])}
                                    -\${zeroDate(obj.noticeList[i].regdate[2])}</p>
                                    <a href="/board/update-cnt.do?id=\${obj.noticeList[i].id}
                                    &type=notice" class="btn btn-outline-secondary btn-sm">
                                    자세히 보기</a>
                                </div>
                            </div>
                        `;
                    }
                    // console.log(htmlStr);
                    $(".card-wrapper").append(htmlStr);
                },
                error: (err) => {
                    console.log(err);
                }
            });
        }
    }
});

 

(참고) notice-list / search-form

<form id="search-form" action="/board/notice-list.do" method="post">
    <input type="hidden" name="pageNum" value="${page.cri.pageNum}">
    <input type="hidden" name="amount" value="${page.cri.amount}">
    <input type="hidden" name="endPage" value="${page.endPage}">
    <div class="row d-flex justify-content-center">
        <div class="col-6">
            <div class="row">
                <div class="col-11">
                    <input type="text" class="form-control w-100" name="searchKeyword" value="${searchMap.searchKeyword}">
                </div>
                <div class="col-1 d-flex align-items-center">
                    <i class="bi bi-search" id="search-icon"></i>
                    <button type="submit" id="btnSearch">검색</button>
                </div>
            </div>
        </div>
    </div>
</form>

 

[boardController 수정]

 - noticeList는 결과값으로 boardDto를 가져온다.

    @PostMapping("/notice-list-ajax.do")
    @ResponseBody
    public Map<String, Object> noticeListAjax(@RequestParam Map<String, String> searchMap, Criteria cri) {
        boardService = applicationContext.getBean("noticeServiceImpl", BoardService.class);

//        cri.setAmount(9);

        List<BoardDto> noticeList = boardService.getBoardList(searchMap, cri);

        Map<String, Object> returnMap = new HashMap<>();

        returnMap.put("noticeList", noticeList);

        return returnMap;
    }

 

(참고)

@Override
public List<BoardDto> getBoardList(Map<String, String> searchMap, Criteria cri) {
    cri.setStartNum((cri.getPageNum() - 1) * cri.getAmount());

    Map<String, Object> paramMap = new HashMap<>();
    paramMap.put("search", searchMap);
    paramMap.put("cri", cri);

    return noticeDao.getNoticeList(paramMap);
}

 


(문제점)

 

페이지 당 9개씩만 나오게끔 지정해주지 않으면 화면에 모든 페이지를 가져와 속도도 느려지고 메모리도 많이 차지한다.

 

이를 위해 3가지만 지정해 두면 된다.

 

1. notice-mapper 수정

 - cri.amount로 9개씩 나오도록 지정

    <select id="getNoticeList" parameterType="map" resultType="board">
...
LIMIT #{cri.amount} OFFSET #{cri.startNum}

 

2. noticeImple 수정

 - 계속 9개씩 더해서 나오게 지정

 - 시작 페이지 = ((현재 페이지 번호 -1) * 한 페이지에 표시해야 할 개수(9개)) 

@Override
public List<BoardDto> getBoardList(Map<String, String> searchMap, Criteria cri) {
    cri.setStartNum((cri.getPageNum() - 1) * cri.getAmount());

 

3. notice-controller 수정

    @PostMapping("/notice-list-ajax.do")
    @ResponseBody
    public Map<String, Object> noticeListAjax(@RequestParam Map<String, String> searchMap, Criteria cri) {
        boardService = applicationContext.getBean("noticeServiceImpl", BoardService.class);

//        cri.setAmount(9);

        List<Map<String, Object>> noticeList = new ArrayList<>();

        boardService.getBoardList(searchMap, cri).forEach(boardDto -> {
            List<BoardFileDto> boardFileDtoList = boardService.getBoardFileList(boardDto.getId());

            Map<String, Object> map = new HashMap<>();

            map.put("boardDto", boardDto);

            if(boardFileDtoList.size() > 0)
                map.put("file", boardFileDtoList.get(0));

            noticeList.add(map);
        });

        Map<String, Object> returnMap = new HashMap<>();

        returnMap.put("noticeList", noticeList);

        return returnMap;
    }