요구사항
1. N:1 연관관계를 이용해, 게시글, 댓글, 회원 엔티티 작성
2. CRUD를 이용해 게시글, 댓글, 회원의 추가, 수정, 삭제 메서드 생성
3. RESTful을 이용해, JSON으로, 댓글은 Ajax를 이용해 비동기 처리
필수 과제
1. @ManyToOne 다대일 연관관계를 설정
2. 연관관계가 없는 상황에서 left (outer) join 처리 방법
3. 즉시 로딩과 지연 로딩 차이와 효율적인 처리 방법
1. 컨트롤러 만들기
->게시물 등록의 경우, 작성자를 현재 존재하는 사용자의 이메일 주소로 지정
목록 : GET
등록 : GET/POST
조회 : GET
수정/삭제 : GET (read멤서드 이용) /POST (modify() / remove())
package com.board.boot3.controller;
import com.board.boot3.dto.BoardDTO;
import com.board.boot3.dto.PageRequestDTO;
import com.board.boot3.service.BoardService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
@RequestMapping("/board/")
@Log4j2
@RequiredArgsConstructor
public class BoardController {
private final BoardService boardService;
@GetMapping("/list")
public void list(PageRequestDTO pageRequestDTO, Model model){
log.info("list............." + pageRequestDTO);
model.addAttribute("result", boardService.getList(pageRequestDTO));
}
@GetMapping("/register")
public void register(){
log.info("regiser 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";
}
@GetMapping({"/read", "/modify" })
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);
}
@PostMapping("/remove")
public String remove(long bno, RedirectAttributes redirectAttributes){
log.info("bno: " + bno);
boardService.removeWithReplies(bno);
redirectAttributes.addFlashAttribute("msg", bno);
return "redirect:/board/list";
}
@PostMapping("/modify")
public String modify(BoardDTO dto,
@ModelAttribute("requestDTO") PageRequestDTO requestDTO,
RedirectAttributes redirectAttributes){
log.info("post modify.........................................");
log.info("dto: " + dto);
boardService.modify(dto);
redirectAttributes.addAttribute("page",requestDTO.getPage());
// redirectAttributes.addAttribute("type",requestDTO.getType());
// redirectAttributes.addAttribute("keyword",requestDTO.getKeyword());
redirectAttributes.addAttribute("bno",dto.getBno());
return "redirect:/board/read";
}
}
2. 화면 처리
#게시물 목록 페이지
<!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="t" th:selected="${pageRequestDTO.type =='c'}" >내용</option>-->
<!-- <option value="t" 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">Writer</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})}">Next</a>
<!-- ,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-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
<script th:inline="javascript">
var msg = [[${msg}]];
console.log(msg);
if(msg){
$(".modal").modal();
}
var searchForm = $("#searchForm");
$('.btn-search').click(function(e){
searchForm.submit();
});
$('.btn-clear').click(function(e){
searchForm.empty().submit();
});
</script>
</th:block>
</th:block>
#게시물 등록 페이지 [GET 방식 / POST 방식
: <form action="@{/board/register}" th:method="post">
: 작성자를 writerEmail로 변경
-> member 테이블의 이메일 주소로 지정
<!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="Writer Email ">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</th:block>
</th:block>
#게시물 조회 페이지
: 특정 번호 클릭시 조회 화면으로 이동
-> 댓글 작업은 조회 페이지에서 댓글의 숫자를 보여주거나, 댓글 입력하는 화면 추가
<!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="gno" 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" rows="5" name="content" 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.type}, keyword =${requestDTO.keyword}-->
<button type="button" class="btn btn-info">List</button>
</a>
</th:block>
</th:block>
#게시물 수정 페이지
: GET/POST 방식
-> 버튼의 이벤트 처리 [자바스크립트]
<!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" rows="5" name="content">[[${dto.content}]]</textarea>
</div>
<div class="form-group">
<label >Writer</label>
<input type="text" class="form-control" name="writer" th:value="${dto.writerEmail}" 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"); //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 pageInfo = $("input[name='page']");
var page = $("input[name='page']");
// var type = $("input[name='type']");
// var keyword = $("input[name='keyword']");
actionForm.empty(); //form 태그의 모든 내용을 지우고
actionForm.append(page);
// actionForm.append(type);
// actionForm.append(keyword);
actionForm
.attr("action", "/board/list")
.attr("method","get");
actionForm.submit();
})
</script>
</th:block>
</th:block>
버튼 이벤트 처리를 위한 자바스크립트 이벤트 메서드
기본 메서드 : 문서가 준비되면 매개변수로 넣은 콜백 함수를 실행하라
$(document).ready(function(){
});
<script th:inline="javascript">
$(document).ready(function(){
alert('First READY');
});
$(document).ready(function(){
alert('Second READY');
});
$(document).ready(function(){
alert('Thired READY');
});
<!-- 함수에 함수에 함수가 들어간 형태 -->
$(document).ready(function() {
<!-- 자바스크립트 변수에는 인라인 표현식,엔티티의 변수 모두 가능-->
var bno = [[${dto.bno}]];
var listGroup = $(".replyList");
$(".replyCount").click(function () {
$.getJSON('/replies/board/' + bno, function (arr) {
console.log(arr);
<!-- 자바스크립트의 출력 함수 console-->
})//end getJSON
})//end click
<!-- 간단한 형식으로 전환-->
<!-- (document).ready 생략 -->
$(function(){
});
<!-- 기본형-->
<!-- 엔티티의 변수에 접근 가능 dto.replyCount 변수를 클릭할 경우-->
$(".replyCount").click(function(){
$.getJSON(){
})
})
</script>
jQuery 메서드의 기본 형태
${'h1').css('color','red');
$ : 선택자, 식별자
.메서드 : .뒤에는 메서드 부분 ('여기에', '이걸') 넣는다.
-> 속성에 값을 넣는 방식
'.클래스 속성'
$('.replyCount').
$('input[name="replyText"]')
$('input[name="replyer"]')
".엔티티 변수"
$(".replyList");
$(".replyCount")
$(".addReply")
$(".modal-footer .btn")
$(".replySave, .replyClose")
기본 형태
.html 함수
let value = $('h1').html();
//h1 의 내용을 변수 value 에 저장합니다.
$('div').html('<a>hello</a>');
//이전 내용을 지우고 새로운 내용을 넣습니다.
<script th:inline="javascript">
$(document).ready(function() {
// 문서 로딩 완료 후, 이러한 것들이 수행 된다.
// 댓글 숫자 클릭시
$(".replyCount").click(function () {
$.getJSON('/replies/board/' + bno, function (arr) {
console.log(arr);
})//end getJSON
})//end click
<!--댓글 처리 함수에서 사용할 날짜 처리하는 함수 만들어 두기-->
//날짜 처리를 위한 함수
function formatTime(str) {
var date = new Date(str);
return date.getFullYear() + '/' +
(date.getMonth() + 1) + '/' +
date.getDate() + ' ' +
date.getHours() + ':' +
date.getMinutes();
}
<!--특정한 게시글 댓글 처리 함수 -->
function loadJSONData() {
$.getJSON('/replies/board/' + bno, function (arr) {
console.log(arr);
var str = "";
$('.replyCount').html(" Reply Count " + arr.length);
$.each(arr, function (idx, reply) {
console.log(reply);
str += ' <div class="card-body" data-rno="' + reply.rno + '"><b>' + reply.rno + '</b>';
str += ' <h5 class="card-title">' + reply.text + '</h5>';
str += ' <h6 class="card-subtitle mb-2 text-muted">' + reply.replyer + '</h6>';
str += ' <p class="card-text">' + formatTime(reply.regDate) + '</p>';
str += ' </div>';
})
listGroup.html(str);
//html() 함수는 선택한 요소 안의 내용을 가져오거나, 다른 내용으로 바꿉니다. 사용방식. h1 의 내용을 변수 value 에 저장합니다
});
}
});
</script>
.attr(attributeName)선택된 요소 집합에서 첫번째 요소의 attributeName에 해당하는 속성값을 반환한다..attr(attributeName, value)선택자에 의해 선택된 요소에 하나 이상의 속성을 부여할 수 있다.여러 속성을 부여할 때, 속성명에 따옴표는 선택사항이다. (class속성을 세팅할 때는 따옴표를 사용) |
: 삭제 버튼
-> form 태그 객체를 저장한 변수 actionForm
-> 변수 actionForm의 action요소에 삭제URL 속성 부여
-> 변수 actionForm의 method요소에 POST방식 속성 부여
: 수정 버튼
-> form 태그 객체를 저장한 변수 actionForm
-> !수정 할지 말지 If문 처리 -> true, return
-> false이면
-> 변수 actionForm의 action요소에 수정URL 속성 부여
-> 변수 actionForm의 method요소에 POST방식 속성 부여
: 목록 버튼
-> page 변수 생성하는데, input태그에서 이름 클래스의 page 변수 전달 : $(input[name='page']");
-> actionFom에 있는 모든 내용을 지우고, 목록 페이지로 이동하는데
-> actionFrom에 페이지 정보를 넣어서 전달한다.
-> 변수 actionForm의 action요소에 목록URL 속성 부여
-> 변수 actionForm의 method요소에 GET방식 속성 부여
<script th:inline="javascript">
var actionForm = $("form"); //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 pageInfo = $("input[name='page']");
var page = $("input[name='page']");
// var type = $("input[name='type']");
// var keyword = $("input[name='keyword']");
actionForm.empty(); //form 태그의 모든 내용을 지우고
actionForm.append(page);
// actionForm.append(type);
// actionForm.append(keyword);
actionForm
.attr("action", "/board/list")
.attr("method","get");
actionForm.submit();
})
</script>
'Server Programming > Spring Boot Backend Programming' 카테고리의 다른 글
[Spring 부트 - 영화 리뷰 프로젝트] 1. M:N (다대다) 관계 설계와 구현 [+ N+1 문제와 엔티티의 특정 속성 로딩 방법] (0) | 2022.10.17 |
---|---|
[Spring 부트 - 댓글 프로젝트] 3-4. 댓글 비동기처리를 위한 @RestController와 JSON 처리 (1) | 2022.10.04 |
[Spring 부트 - 댓글 프로젝트] 3-1. N:1 연관관계의 게시물과 댓글 CRUD (1) | 2022.10.03 |
[Spring 부트 - 방명록 미니 프로젝트] 2-3. 서비스, DTO, 컨트롤러 작성 (2) (0) | 2022.10.02 |
[Spring 부트 - 방명록 미니 프로젝트] 2-2. 서비스, DTO, 컨트롤러 작성 (1) (0) | 2022.10.02 |