
⇒ 페이지를 이동할 수 있는 기능
/board/1
/board/1?cp=2, cp=3 이런식으로 이동(페이지 이동시)
각 네비게이션을 눌렀을 때는 쿼리스트링이 붙지 않지만, 페이지를 이동했을 때는 쿼리스트링 cp가 붙음
공지, 정보, 자유 게시판 모두 동일
/board/1, 2, 3
/** 게시글 목록 조회
*
* {boardCode}
* - /board/xxx : /board 이하 1레벨 자리에 어떤 주소값이 들어오든 모두
* 이 메서드에 매핑하겠다는 것
*
* /board/ 이하 1레벨 자리에 숫자로 된 요청 주소가 작성되어 있을 때만 동작
* -> 정규표현식으로 제한해주면 된다.
*
* {boardCode:[0-9]+}
* - [0-9] : 한 칸에 0~9사이 숫자 입력 가능
* - [0-9]+ : 모든 숫자 가능
*
* @param boardCode : 게시판 종류 구분 번호 (1, 2, 3) => 공지, 정보, 자유게시판
* @return
*/
@GetMapping("{boardCode:[0-9]+}")
// /board/1, 2, 3 -> 뒤 숫자 받을 수 있도록
// 쓰고싶은 변수명을 작성
// 위처럼 작성하면 /board/list 이렇게도 받을 수도 있다는 것
// 숫자만 받아주겠다면 :[] 범위를 작성 -> 0-9는 0~9까지 숫자가 들어올 수 있음
// 10부터는 [0-9]+, 즉 모든 숫자를 다 받아주겠다는 의미
public String selectBoardList(@PathVariable("boardCode") int boardCode) {
// src/main/resources/templates/board/boardList.html
return "board/boardList";
}
PathVariable
검색 유형 & 제목 검색
* @param boardCode : 게시판 종류 구분 번호 (1, 2, 3) => 공지, 정보, 자유게시판
* @param cp : 현재 조회 요청한 페이지 번호(없으면 1)
* @param paramMap(검색 시 이용) : 제출된 파라미터가 모두 저장된 Map
* 검색 시, key, query 담겨있음
* ex) {key=t, query=폭탄}
public String selectBoardList(@PathVariable("boardCode") int boardCode,
@RequestParam(value = "cp", required = false, defaultValue = "1") int cp, Model model,
@RequestParam Map<String, Object> paramMap) {
// paramMap => 검색시 {key=t, query=폭탄}
// 검색 X => {} -> 빈 상태
// src/main/resources/templates/board/boardList.html
return "board/boardList";
}
⇒ 게시글 목록 구성에 필요한 값을 저장한 객체
// 특정 게시판의 지정된 페이지 목록 조회 서비스
@Override
public Map<String, Object> selectBoardList(int boardCode, int cp) {
// 1. 지정된 게시판에서(boardCode)에서
// 삭제되지 않은 게시글 수를 조회
// 2. 1번의 결과 + cp를 이용해서
// Pagination 객체를 생성
// * Pagination 객체 : 게시글 목록 구성에 필요한 값을 저장한 객체
// List, Pagination 객체를 넘겨야 하므로, return 타입이 Map
// 3. 특정 게시판의 지정된 페이지 목록 조회
// -> board가 담긴 리스트를 가져오겠다는 의미
// 4. Pagination 객체 + 목록 조회 결과를 Map으로 묶음
// 5. map 반환
return null;
}package edu.kh.project.board.model.dto;
/* Pagination 뜻 : 목록을 일정 페이지로 분할해서
* 원하는 페이지를 볼 수 있게 하는 것 == 페이징 처리
*
* Pagination 객체를 만드는 이유 : 페이징 처리에 필요한 값을 모아두고, 계산을 하는 객체의 역할로 만들기 위함
*
*
*/
public class Pagination {
private int currentPage; // 현재 페이지 번호
private int listCount; // 전체 게시글 수, selectBoardList에서 listCount로 가져옴
private int limit = 10; // 한 페이지 목록에 보여지는 게시글 수
private int pageSize = 10; // 보여질 페이지 번호 개수, 즉 1~10페이지 나타내는 전체 페이지 크기
// 만약 pageSize가 5라면 1~5, 6~10 이렇게 보임
// 아래 5가지는 값을 직접 바꿔줘야하는 필드
// 가장 첫페이지는 당연히 1페이지 -> 그래서 minPage는 따로없음
// minPage는 가장 첫번째 페이지를 의미
private int maxPage; // 마지막 페이지 번호
private int startPage; // 보여지는 맨 앞 페이지 번호
private int endPage; // 보여지는 맨 뒤 페이지 번호
// startPage, endPage : < or > 버튼 클릭 시, 시작과 끝을 의미
// 1~10이면 startPage = 1, endPage = 10
private int prevPage; // 이전 페이지 모음의 마지막 번호
private int nextPage; // 다음 페이지 모음의 시작 번호
// < or >는 다음페이지의 첫번째 페이지로 가야함
// 만약 1~10페이지에서 >를 누르면 -> 11~20 페이지 중 11페이지 값이 보여야 함
// 만약 현재 21~30이면
// < 눌렀을 때 -> 20번 페이지
// > 눌렀을 때 -> 31번 페이지
}
public Pagination(int currentPage, int listCount) {
this.currentPage = currentPage;
this.listCount = listCount;
calculate();
}
// 4개짜리 생성자(currentPage, listCount, limit, pageSize)
public Pagination(int currentPage, int listCount, int limit, int pageSize) {
this.currentPage = currentPage;
this.listCount = listCount;
this.limit = limit;
this.pageSize = pageSize;
calculate();
}
페이징 처리에 필요한 값을 계산해서 필드에 대입하는 메서드
⇒ (startPage, endPage, maxPage, prevPage, nextPage)
/**
* 페이징 처리에 필요한 값을 계산해서 필드에 대입하는 메서드 (startPage, endPage, maxPage, prevPage,
* nextPage)
*/
private void calculate() {
// 필드 중 limit을 10이라고 가정
// 만약 게시글이 240개면 maxPage는 24
// 만약 게시글이 241개면 maxPage는 25
// 1. maxPage : 최대 페이지 == 즉, 마지막 페이지를 의미하는 것 == 총 페이지 수를 나타냄
// 한 페이지에 게시글이 10개(limit)씩 보여질 경우
// 게시글 수 = 95개라면 -> 10페이지가 나와야 함
// 게시글 수 = 100개라면 -> 10페이지가 나와야 함
// 게시글 수 = 101개라면 -> 11페이지
maxPage = (int) Math.ceil((double) listCount / limit);
// 만약 95 / 10 -> 9
// (double) 95 / 10 -> 9.5
// ceil이면 10.0 -> int로 바꾸면 10
// maxPage는 10
// 2. startPage : 페이지 번호 목록의 시작 번호
// 페이지 번호 목록이 10개(pageSize)씩 보여지는 경우
// 21~30페이지가 있다면 21, 시작 페이지를 구하겠다는 의미
// 현재 페이지가 1 ~ 10 라면 : 1페이지
// 현재 페이지가 11 ~ 20 라면 : 11페이지
startPage = (currentPage - 1) / pageSize * pageSize + 1;
// 만약 현재 5페이지에 있다면
// (5-1) / (pageSize)10 * 10 + 1 => 1
// 시작 페이지 1
// 만약 10페이지라면
// 0 * 0 + 1 => 1
// 만약 11페이지라면
// 1 * 10 + 1 => 11
// 3. endPage : 페이지 번호 목록의 마지막 번호
endPage = pageSize - 1 + startPage;
// 만약 15페이지에 있다면
// 9 + 11 = 20
// 페이지 끝 번호가 최대 페이지 수를 초과한 경우
if (endPage > maxPage) {
// 만약 endPage가 70이고, maxPage가 68이라면
// endPage는 유효한 게시판 값들이 존재하는 maxPage로 대체해줘야 한다.
endPage = maxPage;
}
// 4. prevPage
// prevPage : "<" 클릭 시 이동할 페이지 번호
// (이전 레벨 번호 목록 중 마지막 번호)
if (currentPage <= pageSize) {
// 더 이상 이전으로 갈 페이지가 없을 경우
prevPage = 1;
} else {
prevPage = startPage - 1;
}
// 5. nextPage
// nextPage : ">" 클릭 시 이동할 페이지 번호
// (다음 레벨 번호 목록 중 시작 번호)
// 레벨은 10페이지씩 넘어가는 것을 의미
if (endPage == maxPage) {
// 더 이상 다음으로 넘어갈 페이지가 없을 경우
nextPage = maxPage;
} else {
nextPage = endPage + 1;
}
}
⇒ 지정된 크기만큼 건너 뛰고(offset) 제한된 크기만큼(limit)의 행을 조회하는 객체

int limit = pagination.getLimit(); // 10개
int offset = (cp - 1) * limit;
// offset만큼 건너 뛰고 가져오겠다는 의미
// 만약 2페이지라면 -> (2 - 1) * 10 -> 10
// -> 10개 건너 뛰고 가져오겠다는 의미
RowBounds rowBounds = new RowBounds(offset, limit);
-- 번호 / 제목[댓글개수] / 작성자닉네임 / 작성일 / 조회수 / 좋아요갯수
SELECT BOARD_NO, BOARD_TITLE, MEMBER_NICKNAME, READ_COUNT,
(SELECT COUNT(*)
FROM "COMMENT" C
WHERE C.BOARD_NO = B.BOARD_NO) COMMENT_COUNT, -- 상관쿼리(댓글개수),
(SELECT COUNT(*)
FROM "BOARD_LIKE" L
WHERE L.BOARD_NO = B.BOARD_NO) LIKE_COUNT,
CASE
WHEN SYSDATE - BOARD_WRITE_DATE < 1 / 24 / 60
THEN FLOOR((SYSDATE - BOARD_WRITE_DATE) * 24 * 60 * 60) || '초 전'
WHEN SYSDATE - BOARD_WRITE_DATE < 1 / 24
THEN FLOOR((SYSDATE - BOARD_WRITE_DATE) * 24 * 60) || '분 전'
WHEN SYSDATE - BOARD_WRITE_DATE < 1
THEN FLOOR((SYSDATE - BOARD_WRITE_DATE) * 24) || '시간 전'
ELSE TO_CHAR(BOARD_WRITE_DATE, 'YYYY-MM-DD')
END -- 작성일을 조건에 따라 쓰기
FROM "BOARD" B
JOIN "MEMBER" USING(MEMBER_NO)
WHERE BOARD_DEL_FL = 'N'
ORDER BY BOARD_NO DESC;

<!--
<![CDATA["문자열"]]>
해당 태그 내부에 작성된 문자열은 태그와 같은 특수기호로 해석하지 말고
문자 그대로 인식하라는 태그/명령 (순수 문자 데이터임을 지정해주는 태그)
-->
<select id="selectBoardList">
SELECT BOARD_NO, BOARD_TITLE, MEMBER_NICKNAME,
READ_COUNT,
(SELECT COUNT(*)
FROM "COMMENT" C
WHERE C.BOARD_NO = B.BOARD_NO) COMMENT_COUNT, -- 상관쿼리(댓글개수),
(SELECT COUNT(*)
FROM "BOARD_LIKE" L
WHERE L.BOARD_NO = B.BOARD_NO) LIKE_COUNT,
<![CDATA[
CASE
WHEN SYSDATE - BOARD_WRITE_DATE < 1 / 24 / 60
THEN FLOOR((SYSDATE - BOARD_WRITE_DATE) * 24 * 60 * 60) || '초 전'
WHEN SYSDATE - BOARD_WRITE_DATE < 1 / 24
THEN FLOOR((SYSDATE - BOARD_WRITE_DATE) * 24 * 60) || '분 전'
WHEN SYSDATE - BOARD_WRITE_DATE < 1
THEN FLOOR((SYSDATE - BOARD_WRITE_DATE) * 24) || '시간 전'
ELSE TO_CHAR(BOARD_WRITE_DATE, 'YYYY-MM-DD')
END BOARD_WRITE_DATE
]]>
FROM "BOARD" B
JOIN "MEMBER" USING(MEMBER_NO)
WHERE BOARD_DEL_FL = 'N' AND BOARD_CODE = #{boardCode}
ORDER BY BOARD_NO DESC
</select>

<!-- #lists : 타임리프에서 list 관련 기능을 제공하는 객체 -->
<!-- ${#lists.size(boardList) == 0} -->
<th:block th:if="${#lists.isEmpty(boardList)}">
<tr>
<th colspan="6">게시글이 존재하지 않습니다.</th>
</tr>
</th:block>