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;
}