테이블 추가
BbsBoardEntity
BbsArticleEntity
BbsCommentEntity
BbsBoardListResult
BbsBoardListVo
BbsService
BbsController
BbsBoardListVo가 가진 문자열인 id에 해당하는 게시판이 있는지부터 확인할 것. IBbsMapper에서 BoardEntity를 돌려주는 selectBoardById 생성
BbsMapper.xml만들고 SELECT 추가
BbsService 에서 NOT_FOUND 조건 달고
ARTICLE_PER_PAGE 정규화
IBbsMapper에서 selectArticleCountTotal
BbsMapper.xml에서 selectArticleCountTotal
BbsService 에서
이제 게시글을 가져오자.
BbsArticleEntity는 닉네임이 없기 때문에
dtos 패키지 만들고
BbsBoardListArticleDto 생성. BbsArticleEntity 상속
해당 게시판에 있는 게시글 목록을 들고 오자.
IBbsMapper에서
BbsMapper.xml에서
매개변수는 3개. 각각의 @Param 에 어떤 값을 지정해줄까?
BbsService 에서 LIMIT와 OFFSET을 수동으로 지정
BbsController
board.html 타임리프 넣기
우선 header.html에서 연결
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8">
<header th:fragment="header">
<!-- header, footer, navigation bar 같이 모든 페이지에 보여져야하는 항목인 경우 따로 분리해서 관리한다.
header라는 이름으로 태그 안의 내용을 감싸주었음. index.html에서 다 불러줄 것.-->
<div th:if="${#session != null && #session.getAttribute('userEntity') != null}"
th:with="userEntity=${#session.getAttribute('userEntity')}">
<!-- th:with -> 변수 값을 지정해서 사용할 수 있다. -->
<b th:text="${userEntity.getNickname()}"></b>
<span>님 환영합니다.</span>
<a th:href="@{/user/logout}">로그아웃</a>
</div>
<form method="post"
th:action="@{/user/login(prev=${#request.getRequestURI()})}"
th:if="${#session == null || #session.getAttribute('userEntity') == null}">
<!-- th:action -> form 태그 사용 시, @{ } 을 통해 URL을 명시)-->
<!-- getRequestURL() 는 http://localhost:8080/
getRequestURI() 는 / 라고 나온다.-->
<!-- 로그인을 하면 http://localhost:8080/ 변함없이 이렇게 뜸.-->
<!-- 어느 페이지에서든 submit을 했을 때 action으로 경로 설정해놓은 곳으로 정보를 보낸다.
그게 UserController의 postLogin이다.
request.getRequestURI()는 어느 주소든 다 받게끔 하기 위해 만들어 둔 것.-->
<label>
<span>이메일</span>
<input maxlength="50" name="email" placeholder="이메일" type="email">
</label>
<label>
<span>비밀번호</span>
<input maxlength="100" name="password" placeholder="비밀번호" type="password">
</label>
<input type="submit" value="로그인">
<a th:href="@{/user/login}">비밀번호 재설정</a> <!--user 디렉토리 안 login.html로 연결-->
<a th:href="@{/user/register}">회원가입</a> <!--user 디렉토리 안 register.html로 연결-->
</form>
<nav>
<ul style="display: flex; flex-direction: row; list-style-type: none; margin-block: unset; padding-inline: unset;">
<li style="margin-right: 10px;">
<a th:href="@{/}">홈</a>
</li>
<li style="margin-right: 10px;"> <!-- 반복되는 요소 -->
<a th:href="@{/bbs/board/list/notice}" th:text="${'게시판 이름'}"></a>
</a>
</li>
</ul>
</nav>
</header>
</html>
그리고 board.html
<!--
- 맵핑 주소 : http://localhost:8080/bbs/board/list/{게시판 ID} 및 http://localhost:8080/bbs/board/list/{게시판 ID}/{페이지 번호}
가령, http://localhost:8080/bbs/board/list/notice, http://localhost:8080/bbs/board/list/free/12 등
-->
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8">
<!--/*@thymesVar id="bbsBoardListVo" type="dev.dmchoi.bbsbasic.vos.BbsBoardListVo"*/-->
<h2 th:text="${bbsBoardListVo.getName()}"><!-- 게시판 제목(공지사항, 자유게시판 등) --></h2>
<table>
<thead>
<tr>
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>작성 일시</th>
<th>조회수</th>
</tr>
</thead>
<tbody>
<tr th:if="${bbsBoardListVo.getArticles().size() == 0}"> <!-- 조건하에 표시되는 요소 -->
<td colspan="5" style="padding: 20px 0; text-align: center;">표시할 게시글이 없습니다.</td>
</tr>
<tr th:each="article : ${bbsBoardListVo.getArticles()}" th:object="${article}"> <!-- 반복되는 요소 -->
<td th:text="*{getIndex()}"><!-- 번호 --></td>
<td>
<a th:href="@{'/bbs/article/read/' + *{getIndex()}}"
th:text="${article.getTitle() + ' [' + article.getCommentCount() + ']'}"></a>
</td>
<td th:text="*{getUserNickname()}"><!-- 작성자 --></td>
<td th:text="*{#dates.format(getWrittenAt(), 'yyyy-MM-dd HH:mm:ss')}"><!-- 작성 일시 --></td>
<td th:text="*{getView()}"><!-- 조회수 --></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5">
<form th:action="@{'/bbs/board/search/' + ${bbsBoardListVo.getId()}}">
<label>
<span hidden>검색 기준</span>
<select name="criteria">
<option value="title">제목</option>
<option value="all" selected>제목 + 내용</option>
<option value="writer">작성자</option>
</select>
</label>
<label>
<span hidden>검색어</span>
<input maxlength="50" name="keyword" placeholder="검색어" type="text">
</label>
<input type="submit" value="검색">
</form>
</td>
</tr>
<tr>
<td colspan="5" style="text-align: center;">
<a th:href="@{'/bbs/list/' + ${bbsBoardListVo.getId()} + '/' + ${bbsBoardListVo.getMinPage()}}"
th:if="${bbsBoardListVo.getMinPage() < bbsBoardListVo.getRequestPage()}"><<</a>
<!-- 조건하에 존재하는 요소 -->
<a th:href="@{'/bbs/list/' + ${bbsBoardListVo.getId()} + '/' + ${bbsBoardListVo.getRequestPage() - 1}}"
th:if="${bbsBoardListVo.getMinPage() < bbsBoardListVo.getRequestPage()}"><</a> <!-- 조건하에 존재하는 요소 -->
<th:block th:each="page : ${#numbers.sequence(bbsBoardListVo.getStartPage(), bbsBoardListVo.getEndPage())}">
<a th:href="@{'/bbs/list/' + ${bbsBoardListVo.getId()} + '/' + ${page}}"
th:text="${page}"
th:if="${page != bbsBoardListVo.getRequestPage()}"></a>
<a th:text="${'[' + page + ']'}"
th:if="${page == bbsBoardListVo.getRequestPage()}"></a>
</th:block>
<a th:href="@{'/bbs/list/' + ${bbsBoardListVo.getId()} + '/' + ${bbsBoardListVo.getRequestPage() + 1}}"
th:if="${bbsBoardListVo.getMaxPage() > bbsBoardListVo.getRequestPage()}">>></a>
<!-- 조건하에 존재하는 요소 -->
<a th:href="@{'/bbs/list/' + ${bbsBoardListVo.getId()} + '/' + ${bbsBoardListVo.getMaxPage()}}"
th:if="${bbsBoardListVo.getMaxPage() > bbsBoardListVo.getRequestPage()}">></a> <!-- 조건하에 존재하는 요소 -->
</td>
</tr>
</tfoot>
</table>
</html>
<!--
- 구현할 것 : 페이지 번호에 맞는 게시글 정보 출력, 페이징
- '<<' 요소와 '<' 요소는 현재 페이지 번호가 1을 초과할 때만 표시할 것.
- '>>' 요소와 '>' 요소는 현재 페이지 번호가 최대 페이지 번호 미만일 때만 표시할 것.
- 게시글 제목 끝에 댓글 개수도 함께 표시되게 것.
- 작성자의 이메일이 아닌 닉네임이 표시되게 할 것.
- 작성 일시는 'yyyy-MM-dd HH:mm:ss' 형식으로 표시할 것.
- 페이징 처리할 것.
- FORM 및 XHR 사용은 자유 선택
-->
BbsController 에서 getArticleRead 추가
BbsArticleReadResult 추가
vos에 BbsArticleReadVo 추가
Mapper에 selectArticleByIndex 추가
xml도 추가
BbsService 에서 readArticle 메서드 추가
유저 정보를 가져오기 위해 IUserMapper도 가져옴
기존에 userMapper에서 만들어 두었던 selectUserByIndex 유저 정보를 끌고와서 bbsArticleReadVo의 값과 연결해준다.
BbsArticleReadCommentDto 추가
IBbsMapper에서 selectCommentsForArticleRead메서드 추가
xml도 추가
BbsService 에서 SUCCESS
BbsController 에서 readArticle 불러오고 addObject Vo
read.html 작성
<!--
- 맵핑 주소 : http://localhost:8080/bbs/article/read/{게시글 ID}
가령, http://localhost:8080/bbs/article/read/1, http://localhost:8080/bbs/article/read/12 등
-->
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<!--/*@thymesVar id="bbsArticleReadVo" type="dev.dmchoi.bbsbasic.vos.BbsArticleReadVo"*/-->
<meta charset="UTF-8">
<h2><!-- 게시글 제목 --></h2>
<table th:with="userEntity=${#session != null ? #session.getAttribute('userEntity') : null}">
<thead>
<tr>
<th>제목</th>
<td colspan="5" th:text="${bbsArticleReadVo.getTitle()}"><!-- 제목 --></td>
</tr>
<tr>
<th>작성자</th>
<td th:text="${bbsArticleReadVo.getUserNickname()}"><!-- 작성자 --></td>
<th>조회수</th>
<td th:text="${bbsArticleReadVo.getView()}"><!-- 조회수 --></td>
<th>작성 일시</th>
<td th:text="${#dates.format(bbsArticleReadVo.getWrittenAt(), 'yyyy-MM-dd HH:mm:ss')}"><!-- 작성 일시 --></td>
</tr>
</thead>
<tbody>
<tr>
<td colspan="6" th:text="${bbsArticleReadVo.getContent()}"><!-- 내용 --></td>
</tr>
<tr>
<td colspan="6">
<form th:action="@{/bbs/comment/write}" method="post">
<input name="articleIndex" type="hidden" th:value="${bbsArticleReadVo.getIndex()}">
<label>
<span hidden>댓글 작성</span>
<input maxlength="100" name="content" placeholder="댓글 작성" type="text">
</label>
<input type="submit" value="작성">
</form>
</td>
</tr>
<tr>
<td colspan="6" th:if="${bbsArticleReadVo.getComments().size() == 0}">표시할 댓글이 없습니다.</td> <!-- 조건하에 표시되는 요소 -->
</tr>
<tr th:each="comment : ${bbsArticleReadVo.getComments()}" th:object="${comment}"> <!-- 반복되는 요소 -->
<td th:text="*{getUserNickname()}"><!-- 댓글 작성자 --></td>
<td colspan="4" th:text="*{getContent()}"><!-- 댓글 내용 --></td>
<td>
<span th:text="${#dates.format(comment.getWrittenAt(), 'yyyy-MM-dd HH:mm:ss')}"><!-- 댓글 작성 일시 --></span>
<a th:href="@{'/bbs/comment/delete/' + *{getIndex()}}"
th:if="${userEntity != null && userEntity.getIndex() == comment.getUserIndex()}">삭제</a> <!-- 조건하에 표시되는 요소 -->
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>
<a th:href="@{'/bbs/board/list/' + ${bbsArticleReadVo.getBoardId()} + '/'}">목록</a>
</td>
<td colspan="4"></td>
<td>
<a th:href="@{'/bbs/article/modify/' + ${bbsArticleReadVo.getIndex()}}">수정</a> <!-- 조건하에 표시되는 요소 -->
<a th:href="@{'/bbs/article/delete/' + ${bbsArticleReadVo.getIndex()}}">삭제</a> <!-- 조건하에 표시되는 요소 -->
</td>
</tr>
</tfoot>
</table>
</html>
<!--
- 구현할 것 : 게시글 정보 및 내용 출력, 댓글 정보 및 내용 출력, (조건부) 게시글 삭제, (조건부) 댓글 작성, (조건부) 댓글 삭제, '목록' 링크 제대로
- 게시글 및 댓글 작성자의 이메일이 아닌 닉네임이 표시되게 할 것.
- 게시글 및 댓글 작성 일시는 'yyyy-MM-dd HH:mm:ss' 형식으로 표시할 것.
- FORM 및 XHR 사용은 자유 선택
-->
BbsController에서 getBoardSearch 추가
BbsBoardSearchVo
BbsBoardSearchResult
BbsService에 searchBoard 메서드 추가
BbsBoardListArticleDto를 복사해서 붙여놓고 이름을 BbsBoardSearchArticleDto로. 내용 동일
IBbsMapper 에서 메서드 추가 유저, 제목, 유저/제목, 각각의 카운트까지.
어떤 게시글을 다 적기 위해 그 게시글 제목을 다 적진 않는다.
title이 LIKE 뒤에 나오는 %%로 감싸진 내용을 가졌을 때 라는 의미.
즉 LIKE는 아무거나 앞뒤 상관없고 중간에 무엇만 들어오면 된다.
CONCAT : 뒤에 전달되는 값들을 이어서 문자로 반환해준다.
BbsService에서
위에서 했던 거 그대로 끌고 와서 List를 Search로 변경
search.html
<!--
- 맵핑 주소 : http://localhost:8080/bbs/board/search/{게시판 ID} 및 http://localhost:8080/bbs/board/search/{게시판 ID}/{페이지 번호}
가령, http://localhost:8080/bbs/board/search/notice, http://localhost:8080/bbs/board/search/free/12 등
-->
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8">
<!--/*@thymesVar id="bbsBoardSearchVo" type="dev.dmchoi.bbsbasic.vos.BbsBoardSearchVo"*/-->
<h2 th:text="${bbsBoardSearchVo.getName()}"><!-- 게시판 제목(공지사항, 자유게시판 등) --></h2>
<table>
<thead>
<tr>
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>작성 일시</th>
<th>조회수</th>
</tr>
</thead>
<tbody>
<tr th:if="${bbsBoardSearchVo.getArticles().size() == 0}"> <!-- 조건하에 표시되는 요소 -->
<td colspan="5" style="padding: 20px 0; text-align: center;">표시할 게시글이 없습니다.</td>
</tr>
<tr th:each="article : ${bbsBoardSearchVo.getArticles()}" th:object="${article}"> <!-- 반복되는 요소 -->
<td th:text="*{getIndex()}"><!-- 번호 --></td>
<td>
<a th:href="@{'/bbs/article/read/' + *{getIndex()}}"
th:text="${article.getTitle() + ' [' + article.getCommentCount() + ']'}"></a>
</td>
<td th:text="*{getUserNickname()}"><!-- 작성자 --></td>
<td th:text="*{#dates.format(getWrittenAt(), 'yyyy-MM-dd HH:mm:ss')}"><!-- 작성 일시 --></td>
<td th:text="*{getView()}"><!-- 조회수 --></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5">
<form th:action="@{'/bbs/board/search/' + ${bbsBoardSearchVo.getId()}}">
<label>
<span hidden>검색 기준</span>
<select name="criteria">
<option value="title">제목</option>
<option value="all" selected>제목 + 내용</option>
<option value="writer">작성자</option>
</select>
</label>
<label>
<span hidden>검색어</span>
<input maxlength="50" name="keyword" placeholder="검색어" type="text">
</label>
<input type="submit" value="검색">
</form>
</td>
</tr>
<tr>
<td colspan="5" style="text-align: center;">
<a th:href="@{'/bbs/search/' + ${bbsBoardSearchVo.getId()} + '/' + ${bbsBoardSearchVo.getMinPage()}}"
th:if="${bbsBoardSearchVo.getMinPage() < bbsBoardSearchVo.getRequestPage()}"><<</a>
<!-- 조건하에 존재하는 요소 -->
<a th:href="@{'/bbs/search/' + ${bbsBoardSearchVo.getId()} + '/' + ${bbsBoardSearchVo.getRequestPage() - 1}}"
th:if="${bbsBoardSearchVo.getMinPage() < bbsBoardSearchVo.getRequestPage()}"><</a> <!-- 조건하에 존재하는 요소 -->
<th:block th:each="page : ${#numbers.sequence(bbsBoardSearchVo.getStartPage(), bbsBoardSearchVo.getEndPage())}">
<a th:href="@{'/bbs/search/' + ${bbsBoardSearchVo.getId()} + '/' + ${page}}"
th:text="${page}"
th:if="${page != bbsBoardSearchVo.getRequestPage()}"></a>
<a th:text="${'[' + page + ']'}"
th:if="${page == bbsBoardSearchVo.getRequestPage()}"></a>
</th:block>
<a th:href="@{'/bbs/search/' + ${bbsBoardSearchVo.getId()} + '/' + ${bbsBoardSearchVo.getRequestPage() + 1}}"
th:if="${bbsBoardSearchVo.getMaxPage() > bbsBoardSearchVo.getRequestPage()}">>></a>
<!-- 조건하에 존재하는 요소 -->
<a th:href="@{'/bbs/search/' + ${bbsBoardSearchVo.getId()} + '/' + ${bbsBoardSearchVo.getMaxPage()}}"
th:if="${bbsBoardSearchVo.getMaxPage() > bbsBoardSearchVo.getRequestPage()}">></a> <!-- 조건하에 존재하는 요소 -->
</td>
</tr>
</tfoot>
</table>
</html>
<!--
- 구현할 것 : 페이지 번호에 맞는 게시글 정보 출력, 페이징
- '<<' 요소와 '<' 요소는 현재 페이지 번호가 1을 초과할 때만 표시할 것.
- '>>' 요소와 '>' 요소는 현재 페이지 번호가 최대 페이지 번호 미만일 때만 표시할 것.
- 게시글 제목 끝에 댓글 개수도 함께 표시되게 것.
- 작성자의 이메일이 아닌 닉네임이 표시되게 할 것.
- 작성 일시는 'yyyy-MM-dd HH:mm:ss' 형식으로 표시할 것.
- 페이징 처리할 것.
- FORM 및 XHR 사용은 자유 선택
-->