1) DB에 데이터 삽입
2) boardList.jsp 파일 생성
3) boardList-style.css 파일 생성
4) header.jsp 파일에 nva <a>태그 경로 지정하기
5) DTO 패키지에 Board 클래스 생성(Setter/Getter/ToString)
6) Pagination/ BoardImage / Comment DTO 생성하기
6) BoardController 파일 생성
7) BoardService인터페이스 / BoardServiceImpl 클래스
8) BoardDAO 클래스 생성
9) mybatis-config.xml 별칭(aliase)과 mapper 경로 지정
10) board-mapper.xml 생성
-- 게시판 종류
CREATE TABLE "BOARD_TYPE"(
"BOARD_CODE" NUMBER CONSTRAINT "PK_BOARD_TYPE" PRIMARY KEY,
"BOARD_NAME" VARCHAR2(30) NOT NULL
);
COMMENT ON COLUMN "BOARD_TYPE"."BOARD_CODE"
IS '게시판 코드(SEQ_BOARD_CODE)';
COMMENT ON COLUMN "BOARD_TYPE"."BOARD_NAME"
IS '게시판 이름';
CREATE SEQUENCE SEQ_BOARD_CODE NOCACHE;
-- 게시판 종류 추가
INSERT INTO "BOARD_TYPE" VALUES(SEQ_BOARD_CODE.NEXTVAL, '공지사항');
INSERT INTO "BOARD_TYPE" VALUES(SEQ_BOARD_CODE.NEXTVAL, '자유 게시판');
INSERT INTO "BOARD_TYPE" VALUES(SEQ_BOARD_CODE.NEXTVAL, '테스트 게시판');
INSERT INTO "BOARD_TYPE" VALUES(SEQ_BOARD_CODE.NEXTVAL, '질문 게시판');
INSERT INTO "BOARD_TYPE" VALUES(SEQ_BOARD_CODE.NEXTVAL, '점심 게시판');
COMMIT;
SELECT * FROM "BOARD_TYPE";
----------------------------------------------------------------------
-- [게시판 DB 설정]
CREATE TABLE "BOARD" (
"BOARD_NO" NUMBER NOT NULL,
"BOARD_TITLE" VARCHAR2(150) NOT NULL,
"BOARD_CONTENT" VARCHAR2(4000) NOT NULL,
"B_CREATE_DATE" DATE DEFAULT SYSDATE NOT NULL,
"B_UPDATE_DATE" DATE NULL,
"READ_COUNT" NUMBER DEFAULT 0 NOT NULL,
"BOARD_DEL_FL" CHAR(1) DEFAULT 'N' NOT NULL,
"MEMBER_NO" NUMBER NOT NULL,
"BOARD_CODE" NUMBER NOT NULL
);
COMMENT ON COLUMN "BOARD"."BOARD_NO" IS '게시글 번호(SEQ_BOARD_NO)';
COMMENT ON COLUMN "BOARD"."BOARD_TITLE" IS '게시글 제목';
COMMENT ON COLUMN "BOARD"."BOARD_CONTENT" IS '게시글 내용';
COMMENT ON COLUMN "BOARD"."B_CREATE_DATE" IS '게시글 작성일';
COMMENT ON COLUMN "BOARD"."B_UPDATE_DATE" IS '마지막 수정일(수정 시 UPDATE)';
COMMENT ON COLUMN "BOARD"."READ_COUNT" IS '조회수';
COMMENT ON COLUMN "BOARD"."BOARD_DEL_FL" IS '삭제 여부(N : 삭제X , Y : 삭제O)';
COMMENT ON COLUMN "BOARD"."MEMBER_NO" IS '작성자 회원 번호';
COMMENT ON COLUMN "BOARD"."BOARD_CODE" IS '게시판 코드 번호';
----------------------------------------------------------------------------------------
CREATE TABLE "BOARD_IMG" (
"IMG_NO" NUMBER NOT NULL,
"IMG_PATH" VARCHAR2(300) NOT NULL,
"IMG_RENAME" VARCHAR2(30) NOT NULL,
"IMG_ORIGINAL" VARCHAR2(300) NOT NULL,
"IMG_ORDER" NUMBER NOT NULL,
"BOARD_NO" NUMBER NOT NULL
);
COMMENT ON COLUMN "BOARD_IMG"."IMG_NO" IS '이미지 번호(SEQ_IMG_NO)';
COMMENT ON COLUMN "BOARD_IMG"."IMG_PATH" IS '이미지 저장 폴더 경로';
COMMENT ON COLUMN "BOARD_IMG"."IMG_RENAME" IS '변경된 이미지 파일 이름';
COMMENT ON COLUMN "BOARD_IMG"."IMG_ORIGINAL" IS '원본 이미지 파일 이름';
COMMENT ON COLUMN "BOARD_IMG"."IMG_ORDER" IS '이미지 파일 순서 번호';
COMMENT ON COLUMN "BOARD_IMG"."BOARD_NO" IS '이미지가 첨부된 게시글 번호';
----------------------------------------------------------------------
CREATE TABLE "BOARD_LIKE" (
"BOARD_NO" NUMBER NOT NULL,
"MEMBER_NO" NUMBER NOT NULL
);
COMMENT ON COLUMN "BOARD_LIKE"."BOARD_NO" IS '게시글 번호';
COMMENT ON COLUMN "BOARD_LIKE"."MEMBER_NO" IS '좋아요 누른 회원 번호';
----------------------------------------------------------------------
CREATE TABLE "COMMENT" (
"COMMENT_NO" NUMBER NOT NULL,
"COMMENT_CONTENT" VARCHAR2(4000) NOT NULL,
"C_CREATE_DATE" DATE DEFAULT SYSDATE NOT NULL,
"COMMENT_DEL_FL" CHAR(1) DEFAULT 'N' NOT NULL,
"BOARD_NO" NUMBER NOT NULL,
"MEMBER_NO" NUMBER NOT NULL,
"PARENT_NO" NUMBER
);
COMMENT ON COLUMN "COMMENT"."COMMENT_NO" IS '댓글 번호(SEQ_COMMENT_NO)';
COMMENT ON COLUMN "COMMENT"."COMMENT_CONTENT" IS '댓글 내용';
COMMENT ON COLUMN "COMMENT"."C_CREATE_DATE" IS '댓글 작성일';
COMMENT ON COLUMN "COMMENT"."COMMENT_DEL_FL" IS '댓글 삭제 여부(N : 삭제X, Y : 삭제O)';
COMMENT ON COLUMN "COMMENT"."BOARD_NO" IS '댓글이 작성된 게시글 번호';
COMMENT ON COLUMN "COMMENT"."MEMBER_NO" IS '댓글 작성자 회원 번호';
COMMENT ON COLUMN "COMMENT"."PARENT_NO" IS '부모 댓글 번호';
----------------------------------------------------------------------
ALTER TABLE "BOARD" ADD CONSTRAINT "PK_BOARD" PRIMARY KEY (
"BOARD_NO"
);
ALTER TABLE "BOARD_IMG" ADD CONSTRAINT "PK_BOARD_IMG" PRIMARY KEY (
"IMG_NO"
);
ALTER TABLE "BOARD_LIKE" ADD CONSTRAINT "PK_BOARD_LIKE" PRIMARY KEY (
"BOARD_NO",
"MEMBER_NO"
);
ALTER TABLE "COMMENT" ADD CONSTRAINT "PK_COMMENT" PRIMARY KEY (
"COMMENT_NO"
);
ALTER TABLE "BOARD" ADD CONSTRAINT "FK_MEMBER_TO_BOARD_1" FOREIGN KEY (
"MEMBER_NO"
)
REFERENCES "MEMBER" (
"MEMBER_NO"
);
ALTER TABLE "BOARD" ADD CONSTRAINT "FK_BOARD_TYPE_TO_BOARD_1" FOREIGN KEY (
"BOARD_CODE"
)
REFERENCES "BOARD_TYPE" (
"BOARD_CODE"
);
ALTER TABLE "BOARD_IMG" ADD CONSTRAINT "FK_BOARD_TO_BOARD_IMG_1" FOREIGN KEY (
"BOARD_NO"
)
REFERENCES "BOARD" (
"BOARD_NO"
);
ALTER TABLE "BOARD_LIKE" ADD CONSTRAINT "FK_BOARD_TO_BOARD_LIKE_1" FOREIGN KEY (
"BOARD_NO"
)
REFERENCES "BOARD" (
"BOARD_NO"
);
ALTER TABLE "BOARD_LIKE" ADD CONSTRAINT "FK_MEMBER_TO_BOARD_LIKE_1" FOREIGN KEY (
"MEMBER_NO"
)
REFERENCES "MEMBER" (
"MEMBER_NO"
);
ALTER TABLE "COMMENT" ADD CONSTRAINT "FK_BOARD_TO_COMMENT_1" FOREIGN KEY (
"BOARD_NO"
)
REFERENCES "BOARD" (
"BOARD_NO"
);
ALTER TABLE "COMMENT" ADD CONSTRAINT "FK_MEMBER_TO_COMMENT_1" FOREIGN KEY (
"MEMBER_NO"
)
REFERENCES "MEMBER" (
"MEMBER_NO"
);
ALTER TABLE "COMMENT" ADD CONSTRAINT "FK_COMMENT_TO_COMMENT_1" FOREIGN KEY (
"PARENT_NO"
)
REFERENCES "COMMENT" (
"COMMENT_NO"
);
-- 시퀀스 생성
CREATE SEQUENCE SEQ_BOARD_NO NOCACHE; -- 게시글 번호
CREATE SEQUENCE SEQ_IMG_NO NOCACHE; -- 게시글 이미지 번호
CREATE SEQUENCE SEQ_COMMENT_NO NOCACHE; -- 댓글 번호
-------------------------------------------------------------------------
-- BOARD 테이블 샘플 데이터 삽입(PL/SQL)
BEGIN
FOR I IN 1..1500 LOOP
INSERT INTO BOARD
VALUES( SEQ_BOARD_NO.NEXTVAL,
SEQ_BOARD_NO.CURRVAL || '번째 게시글',
SEQ_BOARD_NO.CURRVAL || '번째 게시글 내용 입니다.',
DEFAULT, DEFAULT, DEFAULT, DEFAULT, 1,
CEIL(DBMS_RANDOM.VALUE(0,5))
);
END LOOP;
END;
/
-- 회원번호 있는 지 조회
SELECT * FROM MEMBER WHERE MEMBER_NO=1;
-- BOARD_TYPE에 컬럼 몇개 인지 조회
SELECT * FROM BOARD_TYPE;
SELECT COUNT(*) FROM BOARD;
COMMIT;
-- BOARD_CODE가 1인 (공지사항)인 게시글을 최신 순으로 조회
-- 단, 삭제 글 제외
SELECT * FROM BOARD
WHERE BOARD_CODE =1
AND BOARD_DEL_FL = 'N'
ORDER BY BOARD_NO DESC;
-- COMMENT 테이블 샘플 데이터 삽입(PL/SQL)
BEGIN
FOR I IN 1..1000 LOOP
INSERT INTO "COMMENT"
VALUES(SEQ_COMMENT_NO.NEXTVAL,
SEQ_COMMENT_NO.CURRVAL || '번째 댓글',
DEFAULT, DEFAULT,
CEIL(DBMS_RANDOM.VALUE(0,1500)),
1, NULL);
END LOOP;
END;
/
SELECT * FROM "COMMENT";
COMMIT;
--게시글 샘플 이미지 넣기
INSERT INTO BOARD_IMG
VALUES (SEQ_IMG_NO.NEXTVAL, '/resources/images/board/',
'20230821141913_00001.png', '빵빵이1', 0, 1498);
INSERT INTO BOARD_IMG
VALUES (SEQ_IMG_NO.NEXTVAL, '/resources/images/board/',
'20230821141913_00002.png', '빵빵이2', 0, 1497);
INSERT INTO BOARD_IMG
VALUES (SEQ_IMG_NO.NEXTVAL, '/resources/images/board/',
'20230821141913_00003.png', '빵빵이3', 0, 1488);
INSERT INTO BOARD_IMG
VALUES (SEQ_IMG_NO.NEXTVAL, '/resources/images/board/',
'20230821141913_00004.png', '빵빵이5', 0, 1495);
INSERT INTO BOARD_IMG
VALUES (SEQ_IMG_NO.NEXTVAL, '/resources/images/board/',
'20230821141913_00005.png', '빵빵이4', 0, 1479);
COMMIT;
SELECT BOARD_NO FROM BOARD
WHERE BOARD_CODE =1
ORDER BY 1 DESC;
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%-- map에 저장된 값을 각각 변수에 저장 --%>
<c:set var="pagination" value="${map.pagination}"/>
<c:set var="boardList" value="${map.boardList}"/>
<%-- <c:set var="boardName" value="${boardTypeList[boardCode-1].BOARD_NAME}"/> --%>
<c:forEach items="${boardTypeList}" var="boardType">
<c:if test="${boardType.BOARD_CODE == boardCode}" >
<c:set var="boardName" value="${boardType.BOARD_NAME}"/>
</c:if>
</c:forEach>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
<link rel="stylesheet" href="/resources/css/board/boardList-style.css">
</head>
<body>
<main>
<jsp:include page="/WEB-INF/views/common/header.jsp"/>
<section class="board-list">
<h1 class="board-name">${boardName}</h1>
<div class="list-wrapper">
<table class="list-table">
<thead>
<tr>
<th>글번호</th>
<th>제목</th>
<th>작성자</th>
<th>작성일</th>
<th>조회수</th>
<th>좋아요</th>
</tr>
</thead>
<tbody>
<c:choose>
<c:when test="${empty boardList}">
<%-- 조회된 게시글 목록 비어있거나 null인 경우 --%>
<!-- 게시글 목록 조회 결과가 비어있다면 -->
<tr>
<th colspan="6">게시글이 존재하지 않습니다.</th>
</tr>
</c:when>
<c:otherwise>
<c:forEach items="${boardList}" var="board">
<!-- 게시글 목록 조회 결과가 있다면-->
<tr>
<td>${board.boardNo}</td>
<td>
<%-- 썸네일이 있을 경우 --%>
<c:if test="${!empty board.thumbnail}" >
<img class="list-thumbnail" src="${board.thumbnail}">
</c:if>
<a href="/board/${boardCode}/${board.boardNo}?cp=${pagination.currentPage}">${board.boardTitle}</a>
[${board.commentCount}]
</td>
<td>${board.memberNickname}</td>
<td>${board.boardCreateDate}</td>
<td>${board.readCount}</td>
<td>${board.likeCount}</td>
</tr>
</c:forEach>
</c:otherwise>
</c:choose>
</tbody>
</table>
</div>
<div class="btn-area">
<!-- 로그인 상태일 경우 글쓰기 버튼 노출 -->
<button id="insertBtn">글쓰기</button>
</div>
<div class="pagination-area">
<ul class="pagination">
<!-- 첫 페이지로 이동 -->
<li><a href="/board/${boardCode}?cp=1"><<</a></li>
<!-- 이전 목록 마지막 번호로 이동 -->
<li><a href="/board/${boardCode}?cp=${pagination.prevPage}"><</a></li>
<!-- 특정 페이지로 이동 -->
<c:forEach var="i" begin="${pagination.startPage}"
end="${pagination.endPage}" step="1">
<c:choose>
<c:when test="${i== pagination.currentPage}">
<!-- 현재 보고있는 페이지 -->
<li><a class="current">${i}</a></li>
</c:when>
<c:otherwise>
<!-- 현재 페이지를 제외한 나머지 -->
<li><a href="/board/${boardCode}?cp=${i}">${i}</a></li>
</c:otherwise>
</c:choose>
</c:forEach>
<!-- 다음 목록 시작 번호로 이동 -->
<li><a href="/board/${boardCode}?cp=${pagination.nextPage}">></a></li>
<!-- 끝 페이지로 이동 -->
<li><a href="/board/${boardCode}?cp=${pagination.maxPage}">>></a></li>
</ul>
</div>
<!-- 검색창 -->
<form action="#" method="get" id="boardSearch">
<select name="key" id="searchKey">
<option value="t">제목</option>
<option value="c">내용</option>
<option value="tc">제목+내용</tion>
<option value="w">작성자</option>
</select>
<input type="text" name="query" id="searchQuery" placeholder="검색어를 입력해주세요.">
<button>검색</button>
</form>
</section>
</main>
<!-- 썸네일 클릭 시 모달창 출력 -->
<div class="modal">
<span id="modalClose">×</span>
<img id="modalImage" src="/resources/images/user.png">
</div>
<jsp:include page="/WEB-INF/views/common/footer.jsp"/>
</body>
</html>
/* section, div{ border : 1px solid black;} */
.board-name{
font-size : 2em;
margin : 20px 0;
}
a{
text-decoration: none;
color : black;
}
a:hover {
text-decoration: underline;
}
.board-list{
/* display: flex; */
width: 1000px;
/* height: 800px; */
margin : 50px auto;
}
.board-title{
margin-left : 30px;
font-size: 2.5em;
}
.list-wrapper{
min-height: 670px;
width: 100%;
}
.list-table{
width: 100%;
padding: 20px;
border-collapse: collapse;
}
.list-table > thead{ background-color: #455ba8; color: white;}
.list-table tr{
height: 60px;
}
.list-table > tbody > tr{
border-bottom: 1px solid gray;
}
.list-table tr > *{ text-align: center; }
.list-table tr > *:nth-of-type(1){ width: 10%; }
.list-table tr > td:nth-of-type(2){
width: 50%;
text-align: left;
padding-left: 50px;
text-decoration: none;
}
.list-table tr > *:nth-of-type(3){ width: 13%; }
.list-table tr > *:nth-of-type(4){ width: 10%; }
.list-table tr > *:nth-of-type(5){ width: 8%; }
.list-table tr > *:nth-of-type(6){ width: 8%; }
.btn-area{
height: 50px;
display: flex;
justify-content: flex-end;
align-items: center;
}
.btn-area > button{
margin-right: 50px;
width: 80px;
height: 40px;
background-color: white;
font-weight: bold;
color: #455ba8;
border: 2px solid #455ba8;
border-radius: 5px;
cursor: pointer;
}
.pagination{
list-style: none;
display: flex;
justify-content: center;
padding: 0;
}
.pagination li{
width: 20px;
margin: 0 5px;
text-align: center;
}
.pagination a{
display: block;
width: 100%;
height: 100%;
}
.current{
font-weight: bold;
background-color: #455ba8;
border-radius: 50%;
color: white !important;
letter-spacing: 0 !important;
}
#boardSearch{
text-align: center;
width: 500px;
height: 30px;
margin: 30px auto;
display: flex;
justify-content: space-between;
}
#boardSearch * {
box-sizing: border-box;
margin: 0;
padding: 0;
}
#boardSearch select{
width: 100px;
}
#boardSearch input{
flex-grow: 1;
margin: 0 10px;
padding-left: 10px;
}
#boardSearch button{
width: 100px;
font-weight: bold;
background-color: #455ba8;
border: none;
color: white;
cursor: pointer;
}
/* 썸네일 */
.list-table .list-thumbnail{
position: absolute;
max-height: 30px;
max-width: 50px;
left: -15px;
top: 17px;
}
.list-table tr > td:nth-of-type(2){
position: relative;
}
/* 모달 */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: none;
z-index: 50;
background-color: rgba(0, 0, 0, 0.4);
}
.modal.show {
display: flex;
animation-name: show;
animation-duration: 0.5s;
z-index: 500;
}
.modal.hide{
animation-name: hide;
animation-duration: 0.5s;
}
#modalImage {
background-color: white;
margin: auto;
border-radius: 10px;
max-width: 100%;
max-height: 100%;
}
/* @keyframes :
애니메이션 중간중간의 특정 지점들을 거칠 수 있는 키프레임들을 설정함으로써 CSS 애니메이션 과정의 중간 절차를 제어할 수 있게 합니다. */
@keyframes show {
from {opacity: 0;}
to {opacity: 1;}
}
@keyframes hide {
from {opacity: 1;}
to {opacity: 0;}
}
/* 닫기 버튼 꾸미기 */
#modalClose {
position: absolute;
top: 20px;
right: 40px;
color: white;
font-size: 50px;
font-weight: bold;
transition-duration: 0.2s;
cursor: pointer;
}
#modalClose:hover{
transform: scale(1.2);
}
<nav>
<ul>
<%-- <li><a href="#">공지사항</a></li>
<li><a href="#">자유 게시판</a></li>
<li><a href="#">질문 게시판</a></li>
<li><a href="#">FAQ</a></li>
<li><a href="#">1:1문의</a></li> --%>
<%-- interceptor을 이용해서 조회된 boardTypeList를
application scope에서 얻어와 화면에 출력 --%>
<c:forEach items="${boardTypeList}" var="boardType">
<li>
<a href="/board/${boardType.BOARD_CODE}">${boardType.BOARD_NAME}</a>
</li>
</c:forEach>
</ul>
</nav>
package edu.kh.project.board.model.dto;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class Board {
private int boardNo;
private String boardTitle;
private String boardContent;
private String boardCreateDate;
private String boardUpdateDate;
private int readCount;
private int boardCode;
// 서브쿼리
private int commentCount; // 댓글 수
private int likeCount; // 좋아요 수
// 회원 join
private String memberNickname;
private int memberNo;
private String profileImage;
private String thumbnail;
// 이미지 목록
private List<BoardImage> imageList;
// 댓글 목록
private List<Comment> commentList;
}
package edu.kh.project.board.model.dto;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class BoardImage {
private int imageNo;
private String imagePath;
private String imageReName;
private String imageOriginal;
private int imageOrder;
private int boardNo;
}
package edu.kh.project.board.model.dto;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class Comment {
private int commentNo;
private String commentContent;
private String commentCreateDate;
private int boardNo;
private int memberNo;
private String commentDeleteFlag;
private int parentNo;
private String memberNickname;
private String profileImage;
}
package edu.kh.project.board.model.dto;
public class Pagination {
// 페이지네이션(페이징 처리)에 필요한 모든 값들을 저장하고 있는 객체
private int currentPage; // 현재 페이지 번호
private int listCount; // 전체 게시글 수
private int limit = 10; // 한 페이지에 보여질 게시글의 수
private int pageSize = 10; // 목록 하단 페이지 번호의 노출 개수
private int maxPage; // 제일 큰 페이지 번호 == 마지막 페이지 번호
private int startPage; // 목록 하단에 노출된 페이지의 시작 번호
private int endPage; // 목록 하단에 노출된 페이지의 끝 번호
private int prevPage; // 목록 하단에 노출된 번호의 이전 목록 끝 번호
private int nextPage; // 목록 하단에 노출된 번호의 다음 목록 시작 번호
// 생성자
public Pagination(int currentPage, int listCount) {
this.currentPage = currentPage;
this.listCount = listCount;
calculatePagination(); // 계산 메서드 호출
}
public int getCurrentPage() {
return currentPage;
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
calculatePagination();
}
public int getListCount() {
return listCount;
}
public void setListCount(int listCount) {
this.listCount = listCount;
calculatePagination();
}
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
this.limit = limit;
calculatePagination();
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
calculatePagination();
}
public int getMaxPage() {
return maxPage;
}
public void setMaxPage(int maxPage) {
this.maxPage = maxPage;
}
public int getStartPage() {
return startPage;
}
public void setStartPage(int startPage) {
this.startPage = startPage;
}
public int getEndPage() {
return endPage;
}
public void setEndPage(int endPage) {
this.endPage = endPage;
}
public int getPrevPage() {
return prevPage;
}
public void setPrevPage(int prevPage) {
this.prevPage = prevPage;
}
public int getNextPage() {
return nextPage;
}
public void setNextPage(int nextPage) {
this.nextPage = nextPage;
}
@Override
public String toString() {
return "Pagination [currentPage=" + currentPage + ", listCount=" + listCount + ", limit=" + limit
+ ", pageSize=" + pageSize + ", maxPage=" + maxPage + ", startPage=" + startPage + ", endPage="
+ endPage + ", prevPage=" + prevPage + ", nextPage=" + nextPage + "]";
}
// 페이징 처리에 필요한 값을 계산하는 메서드
private void calculatePagination() {
// * maxPage 계산 : 최대 페이지 수 == 마지막 페이지 번호
// 전체 게시글 수 : 500개 // 보여지는 게시글 수: 10개
// -> 마지막 페이지 번호는? 500 / 10 = 50
// 전체 게시글 수 : 501개 // 보여지는 게시글 수: 10개
// -> 마지막 페이지 번호는? 501 / 10 = 51 (50.1의 올림 처리)
maxPage = (int)Math.ceil( (double)listCount / limit );
// * startPage : 목록 하단에 노출된 페이지의 시작 번호
// 목록 하단 페이지 번호의 노출 개수가 10개일 때
// 현재 페이지가 1~10 인 경우 : 1
// 현재 페이지가 11~20 인 경우 : 11
// 현재 페이지가 21~30 인 경우 : 21
startPage = (currentPage - 1) / pageSize * pageSize + 1;
// * endPage : 목록 하단에 노출된 페이지의 끝 번호
// 목록 하단 페이지 번호의 노출 개수가 10개일 때
// 현재 페이지가 1~10 인 경우 : 10
// 현재 페이지가 11~20 인 경우 : 20
// 현재 페이지가 21~30 인 경우 : 30
endPage = startPage + pageSize - 1;
// 만약에 endPage가 maxPage를 초과하는 경우
if(endPage > maxPage) {
endPage = maxPage;
}
// * prevPage(<) : 목록 하단에 노출된 번호의 이전 목록 끝 번호
// * nextPage(>) : 목록 하단에 노출된 번호의 다음 목록 시작 번호
// 현재 페이지가 1~10 일 경우
// < : 1 페이지
// > : 11 페이지
// 현재 페이지가 11~20 일 경우
// < : 10 페이지
// > : 21 페이지
// 현재 페이지가 41~50 일 경우 (maxPage가 50)
// < : 40 페이지
// > : 50 페이지
if(currentPage <= pageSize) {
prevPage = 1;
}else {
prevPage = startPage - 1;
}
if(endPage == maxPage) {
nextPage = maxPage;
}else {
nextPage = endPage + 1;
}
}
}
@PathVariable: URL 경로에 있는 값을 매개변수로 이용할 수 있게 하는 어노테이션
+ request Scope에 세팅
@PathVariable과 Query String
- @PathVariable을 사용하는 경우 = 자원(resource) 구분/식별하는 용도
ex) https://github.com/complete0415 ex) https://github.com/testUser ex) /board/1 -> 1번 (공지사항)게시판 ex) /board/2 -> 2번 (자유게시판)게시판- Query String을 사용하는 경우 = 정렬 필터링
ex) search.naver.com?query=날씨 ex) search.naver.com?query=점심 ex) /board2/insert?code=1 ex) /board2/insert?code=2 -> 삽입이라는 공통된 동작을 수행 단, code에 따라 어디에 삽입할 지 지정(필터링)
ex) /board/list?order=recent (최신순) ex) /board/list?order=most (인기순)
package edu.kh.project.board.controller;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import edu.kh.project.board.model.service.BoardService;
@SessionAttributes({"loginMember"})
@RequestMapping("/board")
@Controller
public class BoardController {
@Autowired
private BoardService service;
/* 목록 조회 : /board/1?cp=1 (cp : current page(현재페이지))
* 상세 조회 : /board/1/1500?cp=1
*
* ***컨트롤러 따로 생성 ***
* 삽입 : /board2/inssert?code=1(code ==BOARD_CODE , 게시판 종류)
* 수정 : /board2/update?code=1&no=1500 (no == BOARD_NO , 게시글 번호)
* 삭제 : /board2/delete?code=1&no=1500
* */
// @PathVariable
// URL 경로에 있는 값을 메개 변수로 이용할 수 있게 하는 어노테이션
// + request Scope에 세팅
// @PathVariable을 사용하는 경우
// - 자원(resource) 구분/식별
// ex) https://github.com/complete0415
// ex) https://github.com/testUser
// ex) /board/1 -> 1번 (공지사항)게시판
// ex) /board/2 -> 2번 (자유게시판)게시판
// Query String을 사용하는 경우
// - 정렬, 필터링
// ex) search.naver.com?query=날씨
// ex) search.naver.com?query=점심
// ex) /board2/insert?code=1
// ex) /board2/insert?code=2
// -> 삽입이라는 공통된 동작을 수행
// 단, code에 따라 어디에 삽입할 지 지정(필터링)
// ex) /board/list?order=recent (최신순)
// ex) /board/list?order=most (인기순)
//게시글 목록 조회
@GetMapping("/{boardCode}")
public String selectBoardList(@PathVariable("boardCode") int boardCode
, @RequestParam(value="cp", required = false, defaultValue = "1") int cp
, Model model) {
// boardCode 확인
//System.out.println("boardCode: "+ boardCode);
// 게시글을 목록 조회하는 service호출
Map<String, Object> map = service.selectBoardList(boardCode, cp);
//조회 결과를 request scope에 세팅 후 forward
model.addAttribute("map", map);
return "board/boardList";
}
}
package edu.kh.project.board.model.service;
import java.util.List;
import java.util.Map;
public interface BoardService {
/** 게시판 종류 목록 조회
* @return boardTypeList
*/
List<Map<String, Object>> selectBoardTypeList();
/** 게시글 목록 조회
* @param boardCode
* @param cp
* @return map
*/
Map<String, Object> selectBoardList(int boardCode, int cp);
}
package edu.kh.project.board.model.service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import edu.kh.project.board.model.dao.BoardDao;
import edu.kh.project.board.model.dto.Board;
import edu.kh.project.board.model.dto.Pagination;
@Service
public class BoardServiceImpl implements BoardService{
@Autowired
private BoardDao dao;
//게시판 종류 목록 조회
@Override
public List<Map<String, Object>> selectBoardTypeList() {
return dao.selectBoardTypeList();
}
// 게시글 목록 조회
@Override
public Map<String, Object> selectBoardList(int boardCode, int cp) {
// 1. 특정 게시판에 삭제되지 않은 게시글 수 조회
int listCount = dao.getListCount(boardCode);
// 2. 1번 조회 결과 + cp를 이용해서 Pagination 객체 생성
// -> 내부에 필드가 모두 계산 되어서 초기화 됨
Pagination pagination = new Pagination(cp, listCount);
// 3. 특정게시판에서
// 현재 페이지에 해당하는 부분에 대한 게시글 목록 조회
// (어떤 게시판(boardCode)에서
// 몇 페이지 (pagination.crrentPage)에 대한
// 게시글 몇 개 (pagination.limit) 조회)
List<Board> boardList = dao.selectBoardList(pagination, boardCode);
// 4. paginatio, boardList를 Map에 담아서 반환
Map<String, Object> map = new HashMap<String, Object>();
map.put("pagination", pagination);
map.put("boardList", boardList);
return map;
}
}
RowBounds 객체
- 마이바티스 에서 페이징 처리를 위해 제공하는 객체 - offset 만큼 건너 뛰고 그 다음 지정된 행의 객수 (limit) 만큼 조회
[순서]
1) offset 계산
2) RowBounds 객체생성
3) selectList("namespace.id", 파라미터, Rowbounds)호출
package edu.kh.project.board.model.dao;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.session.RowBounds;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import edu.kh.project.board.model.dto.Board;
import edu.kh.project.board.model.dto.Pagination;
@Repository
public class BoardDao {
@Autowired
private SqlSessionTemplate sqlSession;
/** 게시판 동류목록 조회
* @return boardTypeList
*/
public List<Map<String, Object>> selectBoardTypeList() {
return sqlSession.selectList("boardMapper.selectBoardTypeList");
}
/** 특정기세판에 삭제되지 않은 게시글 수 조회
* @param boardCode
* @return listCount
*/
public int getListCount(int boardCode) {
return sqlSession.selectOne("boardMapper.getListCount", boardCode);
}
/** 특정게시판에서 현재 페이지에 해당하는 부분에 대한 게시글 목록 조회
* @param pagination
* @param boardCode
* @return boardList
*/
public List<Board> selectBoardList(Pagination pagination, int boardCode) {
// RowBounds 객체
// - 마이바티스 에서 페이징 처리를 위해 제공하는 객체
// - offset 만클 건너 뛰고
// 그 다음 지정된 행의 객수 (limit) 만큼 조회
// 1) offset 계산
int offset
= (pagination.getCurrentPage() -1) * pagination.getLimit();
// 2) Row Bounds객체 생성
RowBounds rowBounds = new RowBounds(offset, pagination.getLimit());
// 3) sselectList("namespace.id", 파라미터, Rowbounds)호출
return sqlSession.selectList("boardMapper.selectBoardList", boardCode, rowBounds);
}
}
별칭 및 mapper 위치 등록
<!-- 별칭 작성 -->
<typeAliases>
<typeAlias type="edu.kh.project.board.model.dto.Board" alias="Board"/>
</typeAliases>
<!-- mapper파일 위치등록 -->
<mappers>
<mapper resource="/mappers/board-mapper.xml">
</mappers>
- resultType 또는 resultMap 꼭 써줄것
- CDATA 태그 : 해당 태그 내부에 작성된 것은 모두 문자로 취급
- paramType은 생략가능
<?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="boardMapper">
<!--boardDTO에 대한 ReaultMap -->
<resultMap type="Board" id="board_rm">
<id property="boardNo" column="BOARD_NO"/>
<result property="boardTitle" column="BOARD_TITLE"/>
<result property="boardContent" column="BOARD_CONTENT"/>
<result property="boardCreateDate" column="B_CREATE_DATE"/>
<result property="boardUpdateDate" column="B_UPDATE_DATE"/>
<result property="readCount" column="READ_COUNT"/>
<result property="commentCount" column="COMMENT_COUNT"/>
<result property="likeCount" column="LIKE_COUNT"/>
<result property="memberNickname" column="MEMBER_NICKNAME"/>
<result property="memberNo" column="MEMBER_NO"/>
<result property="profileImage" column="PROFILE_IMG"/>
<result property="thumbnail" column="THUMBNAIL"/>
</resultMap>
<!-- resultType이 "map"인 경우
K: 컬럼 명 (BOARD_CODE , BOARD_NAME)
V: 컬럼 값 ( 1 , 공지사항 )
-->
<!-- 게시판 종류목록 조회 -->
<select id="selectBoardTypeList" resultType="map">
SELECT * FROM BOARD_TYPE ORDER BY 1
</select>
<!-- 특정게시판의 삭제되지 않은 게시글 수 조회 -->
<select id="getListCount" resultType="_int">
SELECT COUNT(*) FROM BOARD
WHERE BOARD_DEL_FL = 'N'
AND BOARD_CODE = #{boardCode}
</select>
<!-- CDATA 태그 : 해당 태그 내부에 작성된 것은 모두 문자로 취급-->
<!-- 게시글 목록 조회 -->
<select id="selectBoardList" resultMap="board_rm">
SELECT BOARD_NO, BOARD_TITLE, MEMBER_NICKNAME, READ_COUNT,
<![CDATA[
CASE
WHEN SYSDATE - B_CREATE_DATE < 1/24/60
THEN FLOOR( (SYSDATE - B_CREATE_DATE) * 24 * 60 * 60 ) || '초 전'
WHEN SYSDATE - B_CREATE_DATE < 1/24
THEN FLOOR( (SYSDATE - B_CREATE_DATE) * 24 * 60) || '분 전'
WHEN SYSDATE - B_CREATE_DATE < 1
THEN FLOOR( (SYSDATE - B_CREATE_DATE) * 24) || '시간 전'
ELSE TO_CHAR(B_CREATE_DATE, 'YYYY-MM-DD')
END B_CREATE_DATE,
]]>
(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,
(SELECT IMG_PATH || IMG_RENAME FROM BOARD_IMG I
WHERE I.BOARD_NO = B.BOARD_NO
AND IMG_ORDER = 0) THUMBNAIL
FROM "BOARD" B
JOIN "MEMBER" USING(MEMBER_NO)
WHERE BOARD_DEL_FL = 'N'
AND BOARD_CODE = #{boardCode}
ORDER BY BOARD_NO DESC
</select>
</mapper>
--특정게시판에 삭제 되지 않은 게시글 수 조회----
SELECT COUNT(*) FROM BOARD
WHERE BOARD_DEL_FL = 'N'
AND BOARD_CODE =1;
-- 특정게시판에 목록조회---------------
-- 1. 최신순서
-- 2. 1page(1~10행)
-- 3. 삭제된 글 제외
-- 마이바티스 O
--> RowBounds 객체 이용
SELECT BOARD_NO, BOARD_TITLE, MEMBER_NICKNAME,
TO_CHAR(B_CREATE_DATE, 'YYYY-MM-DD') B_CREATE_DATE,
READ_COUNT
FROM "BOARD"
JOIN "MEMBER" USING(MEMBER_NO)
WHERE BOARD_DEL_FL = 'N'
AND BOARD_CODE = 1
ORDER BY BOARD_NO DESC;
-- 마이바티스가 x 기본으로 해야하는
-- 1~10행 조회
SELECT * FROM(SELECT A.* FROM (
SELECT ROWNUM NUM, BOARD_NO, BOARD_TITLE, MEMBER_NICKNAME,
TO_CHAR(B_CREATE_DATE, 'YYYY-MM-DD') B_CREATE_DATE,
READ_COUNT
FROM "BOARD"
JOIN "MEMBER" USING(MEMBER_NO)
WHERE BOARD_DEL_FL = 'N'
AND BOARD_CODE = 1
ORDER BY BOARD_NO DESC
) A
)
WHERE NUM BETWEEN 1 AND 10;
정리 너무너무 잘하셨네요 >< 🥰