
이전 guestbook 예제의 템플릿을 먼저 가져오겠습니다.

그리고 BoardController를 생성한 뒤, 목록화면에 접근을 처리하는 메서드까지 생성해두겠습니다.
BoardController
package org.zerock.board.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
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.RequestMapping;
import org.zerock.board.dto.PageRequestDTO;
import org.zerock.board.service.BoardService;
@Controller
@Log4j2
@RequestMapping("/board")
@RequiredArgsConstructor
public class BoardController {
@Autowired
private final BoardService boardService;
@GetMapping("/list")
public void list(PageRequestDTO pageRequestDTO, Model model) {
log.info("list......." + pageRequestDTO);
model.addAttribute("result", boardService.getList(pageRequestDTO));
}
}
다음으로는 list 페이지를 생성하겠습니다.

list.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic::setContent(~{this::content})}">
<th:block th:fragment="content">
<h1 class="mt-4">Board List Page
<span>
<a th:href="@{/board/register}">
<button type="button" class="btn btn-outline-primary">REGISTER</button>
</a>
</span>
</h1>
<form action="/board/list" method="get" id="searchForm">
<div class="input-group">
<input type="hidden" name="page" value="1">
<div class="input-group-prepend">
<select class="custom-select" name="type">
<option th:selected="${pageRequestDTO.type == null}">------</option>
<option value="t" th:selected="${pageRequestDTO.type == 't'}">제목</option>
<option value="c" th:selected="${pageRequestDTO.type == 'c'}">내용</option>
<option value="w" th:selected="${pageRequestDTO.type == 'w'}">작성자</option>
<option value="tc" th:selected="${pageRequestDTO.type == 'tc'}">제목 + 내용</option>
<option value="tcw" th:selected="${pageRequestDTO.type == 'tcw'}">제목 + 내용 + 작성자</option>
</select>
</div>
<input class="form-control" name="keyword" th:value="${pageRequestDTO.keyword}">
<div class="input-group-append" id="button-addon4">
<button class="btn btn-outline-secondary btn-search" type="button">Search</button>
<button class="btn btn-outline-secondary btn-clear" type="button">Clear</button>
</div>
</div>
</form>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Title</th>
<th scope="col">Content</th>
<th scope="col">Regdate</th>
</tr>
</thead>
<tbody>
<tr th:each="dto : ${result.dtoList}">
<th scope="row">
<a th:href="@{/board/read(bno=${dto.bno}, page=${result.page}, type=${pageRequestDTO.type}, keyword=${pageRequestDTO.keyword})}">
[[${dto.bno}]]
</a>
</th>
//수정
<td>[[${dto.title}]] ----------- [<b th:text="${dto.replyCount}"></b>]</td>
<td>[[${dto.writerName}]] <small>[[${dto.writerEmail}]]</small></td>
<td>[[${#temporals.format(dto.regDate, 'yyyy/MM/dd')}]]</td>
</tr>
</tbody>
</table>
<ul class="pagination h-100 justify-content-center align-items-center">
<li class="page-item" th:if="${result.prev}">
<a class="page-link" th:href="@{/board/list(page=${result.start - 1}, type=${pageRequestDTO.type}, keyword=${pageRequestDTO.keyword})}" tabindex="-1">Previous</a>
</li>
<li th:class=" 'page-item ' + ${result.page == page ? 'active' : ''} " th:each="page: ${result.pageList}">
<a class="page-link" th:href="@{/board/list(page=${page}, type=${pageRequestDTO.type}, keyword=${pageRequestDTO.keyword})}">
[[${page}]]
</a>
</li>
<li class="page-item" th:if="${result.next}">
<a class="page-link" th:href="@{/board/list(page=${result.end + 1}, type=${pageRequestDTO.type}, keyword=${pageRequestDTO.keyword})}">Next</a>
</li>
</ul>
<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p>Modal body text goes here.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary">Save changes</button>
<button type="button" class="btn btn-secondary close" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script th:inline="javascript">
var msg = [[${msg}]];
console.log(msg);
const $modal = $(".modal")
if(msg) {
$('.modal').show();
}
$(".close").on("click", () => {
$modal.hide()
});
var searchForm = $("#searchForm");
$('.btn-search').click(function(e) {
searchForm.submit();
});
$('.btn-clear').click(function(e) {
searchForm.empty().submit();
});
</script>
</th:block>
</th:block>
guestbook 예제의 list.html에서 Ctrl + F로 guestbook을 찾아 board로 바꾸고, gno를 bno로만 바꿉니다. 그리고 리스트의 내용에 해당하는 table을 위와 같이 수정하면 동작합니다.

제목 옆에 댓글 개수가 보입니다.
다음으로는 등록 화면을 만들겠습니다. 컨트롤러에 GET, POST 메서드를 추가합니다.
BoardController
@GetMapping("/register")
public void register() {
log.info("register get.........");
}
@PostMapping("/register")
public String registerPost(BoardDTO dto, RedirectAttributes redirectAttributes) {
log.info("dto....." + dto);
Long bno = boardService.register(dto);
log.info("BNO: " + bno);
redirectAttributes.addFlashAttribute("msg", bno);
return "redirect:/board/list";
}
register 페이지도 guestbook 예제와 거의 동일하고, guestbook을 board로, writer부분을 writer email을 입력받을 수 있도록 변경합니다.
register.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content})}">
<th:block th:fragment="content">
<h1 class="mt-4">Board Register Page</h1>
<form th:action="@{/board/register}" th:method="post">
<div class="form-group">
<label>Title</label>
<input type="text" class="form-control" name="title" placeholder="Enter Title">
</div>
<div class="form-group">
<label>Content</label>
<textarea class="form-control" rows="5" name="content"></textarea>
</div>
<div class="form-group">
<label>Writer Email</label>
<input type="email" class="form-control" name="writerEmail" placeholder="Enter Writer Email">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</th:block>
</th:block>
이제 생성하면 정상적으로 등록됩니다. 여기서 Writer의 이메일이 데이터베이스에 존재하지 않는다면 에러가 발생하므로, 존재하는 이메일로 등록해야 합니다.


마찬가지로 게시물 조회 페이지도 처리해줍니다.
read.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content})}">
<th:block th:fragment="content">
<h1 class="mt-4">Board Read Page</h1>
<div class="form-group">
<label>Bno</label>
<input type="text" class="form-control" name="bno" th:value="${dto.bno}" readonly>
</div>
<div class="form-group">
<label>Title</label>
<input type="text" class="form-control" name="title" th:value="${dto.title}" readonly>
</div>
<div class="form-group">
<label>Content</label>
<textarea class="form-control" name="content" rows="5" readonly>[[${dto.content}]]</textarea>
</div>
<div class="form-group">
<label>Writer</label>
<input type="text" class="form-control" name="writer" th:value="${dto.writerName}" readonly>
</div>
<div class="form-group">
<label>RegDate</label>
<input type="text" class="form-control" name="regDate" th:value="${#temporals.format(dto.regDate, 'yyyy/MM/dd HH:mm:ss')}" readonly>
</div>
<div class="form-group">
<label>ModDate</label>
<input type="text" class="form-control" name="modDate" th:value="${#temporals.format(dto.modDate, 'yyyy/MM/dd HH:mm:ss')}" readonly>
</div>
<a th:href="@{/board/modify(bno=${dto.bno}, page=${requestDTO.page}, type=${requestDTO.type}, keyword=${requestDTO.keyword})}">
<button type="button" class="btn btn-primary">Modify</button>
</a>
<a th:href="@{/board/list(page=${requestDTO.page}, type=${requestDTO.page}, keyword=${requestDTO.keyword})}">
<button type="button" class="btn btn-info">List</button>
</a>
</th:block>
</th:block>
마찬가지로 read 페이지도 이전 예제에서 몇가지만 수정해서 적용합니다.
BoardController
@GetMapping("/read")
public void read(@ModelAttribute("requestDTO") PageRequestDTO pageRequestDTO, Long bno, Model model) {
log.info("bno: " + bno);
BoardDTO boardDTO = boardService.get(bno);
log.info(boardDTO);
model.addAttribute("dto", boardDTO);
}
수정 페이지에 삭제 버튼까지 있으므로 둘을 한번에 처리하겠습니다. 수정의 GET은 read와 동일하므로 @GetMapping에 /modify를 추가합니다. POST는 아래와 같이 작성합니다.
BoardController
@PostMapping("/modify")
public String modify(BoardDTO dto, @ModelAttribute("requestDTO") PageRequestDTO pageRequestDTO, RedirectAttributes redirectAttributes) {
log.info("post modify..........");
log.info("dto: " + dto);
boardService.modify(dto);
redirectAttributes.addAttribute("page", pageRequestDTO.getPage());
redirectAttributes.addAttribute("type", pageRequestDTO.getType());
redirectAttributes.addAttribute("keyword", pageRequestDTO.getKeyword());
redirectAttributes.addAttribute("bno", dto.getBno());
return "redirect:/board/read";
}
@PostMapping("/delete")
public String delete(long bno, RedirectAttributes redirectAttributes) {
log.info("bno: " + bno);
boardService.removeWithReplies(bno);
redirectAttributes.addFlashAttribute("msg", bno);
return "redirect:/board/list";
}
modify.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content})}">
<th:block th:fragment="content">
<h1 class="mt-4">Board Modify Page</h1>
<form action="/board/modify" method="post">
<input type="hidden" name="page" th:value="${requestDTO.page}">
<input type="hidden" name="type" th:value="${requestDTO.type}">
<input type="hidden" name="keyword" th:value="${requestDTO.keyword}">
<div class="form-group">
<label>Bno</label>
<input type="text" class="form-control" name="bno" th:value="${dto.bno}" readonly>
</div>
<div class="form-group">
<label>Title</label>
<input type="text" class="form-control" name="title" th:value="${dto.title}">
</div>
<div class="form-group">
<label>Content</label>
<textarea class="form-control" name="content" rows="5">[[${dto.content}]]</textarea>
</div>
<div class="form-group">
<label>Writer</label>
<input type="text" class="form-control" name="writer" th:value="${dto.writerName}" readonly>
</div>
<div class="form-group">
<label>RegDate</label>
<input type="text" class="form-control" th:value="${#temporals.format(dto.regDate, 'yyyy/MM/dd HH:mm:ss')}" readonly>
</div>
<div class="form-group">
<label>ModDate</label>
<input type="text" class="form-control" th:value="${#temporals.format(dto.modDate, 'yyyy/MM/dd HH:mm:ss')}" readonly>
</div>
</form>
<button type="button" class="btn btn-primary modifyBtn">Modify</button>
<button type="button" class="btn btn-info listBtn">List</button>
<button type="button" class="btn btn-danger removeBtn">Remove</button>
<script th:inline="javascript">
var actionForm = $("form");
$(".removeBtn").click(function(){
actionForm
.attr("action", "/board/remove")
.attr("method","post");
actionForm.submit();
});
$(".modifyBtn").click(function() {
if(!confirm("수정하시겠습니까?")) {
return;
}
actionForm
.attr("action", "/board/modify")
.attr("method", "post")
.submit();
});
$(".listBtn").click(function() {
var page = $("input[name='page']");
var type = $("input[name='type']");
var keyword = $("input[name='keyword']");
actionForm.empty();
actionForm.append(page);
actionForm.append(type);
actionForm.append(keyword);
actionForm
.attr("action", "/board/list")
.attr("method", "get");
actionForm.submit();
})
</script>
</th:block>
</th:block>