글작성하기
로그인 후 네비게이션 바의 글쓰기 눌렀을 때 /board/saveForm으로 이동하도록 설정
BoardController.java
@Controller
public class BoardController {
...
// USER 권한 필요
@GetMapping("/board/saveForm")
public String saveFrom() {
return "board/saveForm";
}
}
board/saveForm.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!-- 현재 board 파일 안이므로 ../로 올라가줘야 한다. -->
<%@ include file="../layout/header.jsp"%>
<div class="container">
글쓰기 화면
</div>
<!-- 현재 board 파일 안이므로 ../로 올라가줘야 한다. -->
<%@ include file="../layout/footer.jsp"%>
잘 연결된다.
summer note 적용하기
https://summernote.org/getting-started/
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.js"></script>
board/saveForm.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!-- 현재 board 파일 안이므로 ../로 올라가줘야 한다. -->
<%@ include file="../layout/header.jsp"%>
<div class="container">
<form>
<div class="form-group">
<label for="title">Title</label> <input type="text" class="form-control" placeholder="Enter title" id="title">
</div>
<div class="form-group">
<label for="comment">Content:</label>
<textarea class="form-control summernote" rows="5" id="content"></textarea>
</div>
</form>
<!-- JSON으로 요청할 거라서 form 밖으로 뺀다. -->
<button id="btn-save" class="btn btn-primary">글쓰기완료</button>
</div>
<!-- summer note script -->
<script>
$('.summernote').summernote({
tabsize : 2,
height : 300
});
</script>
<script src="/js/board.js"></script>
<!-- 현재 board 파일 안이므로 ../로 올라가줘야 한다. -->
<%@ include file="../layout/footer.jsp"%>
js/board.js
let index = {
init: function() {
$("#btn-save").on("click", () => {
this.save();
});
},
save: function(){
let data = {
title: $("#title").val(),
content: $("#content").val()
};
$.ajax({
type: "POST",
url: "/api/board",
data: JSON.stringify(data),
contentType: "application/json; charset=utf-8",
dataType: "json"
}).done(function(resp){
alert("글쓰기가 완료되었습니다.");
location.href="/";
}).fail(function(error){
alert(JSON.stringify(error));
});
},
}
index.init();
BoardApiController.java
@RestController
public class BoardApiController {
@Autowired
private BoardService boardService;
@PostMapping("/api/board") // board와 user 정보가 필요하다!
public ResponseDto<Integer> save(@RequestBody Board board, @AuthenticationPrincipal PrincipalDetail principal) {
// user 정보를 얻어오기 위해 PrincipalDetail에 @Getter를 적어줬다.
boardService.글쓰기(board, principal.getUser());
return new ResponseDto<Integer>(HttpStatus.OK.value(), 1);
}
}
BoardRepository.java
public interface BoardRepository extends JpaRepository<Board, Integer>{
}
BoardService.java
@Service
public class BoardService {
@Autowired
private BoardRepository boardRepository;
@Transactional
public void 글쓰기(Board board, User user) { // title, content
board.setCount(0);
board.setUser(user);
boardRepository.save(board);
}
}
흐름 정리
1. 글쓰기 요청
2. BoardController.java의 @GetMapping("/board/saveForm")로 매핑
3. saveForm에서 글쓰기 완료 버튼을 누르면
4. boards.js에서 title과 content 데이터를 JSON으로 /api/board에 보냄
5. BoardApiController에서 @PostMapping("/api/board")매핑
6. board에는 title, content, user 정보가 필요하므로 user 가져온 후
7. boardService의 글쓰기 함수 호출해서 DB에 저장
글 목록 보기
이제 작성한 글들을 메인 페이지에서 볼 수 있도록 해보자.
BoardController에서 "/"로 갈 때 데이터를 가져가야 한다. 스프링에서는 데이터를 가져갈 때 모델이 필요하다.
BoardController.java
@Controller
public class BoardController {
@Autowired
private BoardService boardService;
@GetMapping({"", "/"})
public String index(Model model) {
model.addAttributes("boards", boardService.글목록());
return "index";
}
...
}
BoardService.java
@Service
public class BoardService {
...
public List<Board> 글목록() {
return boardRepository.findAll();
}
}
이제 index.jsp에서 가져온 글목록을 뿌려주기만 하면 된다!
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ include file="layout/header.jsp"%>
<div class="container">
<c:forEach var="board" items="${boards}">
<div class="card m-2">
<div class="card-body">
<h4 class="card-title">${board.title }</h4>
<a href="#" class="btn btn-primary">상세 보기</a>
</div>
</div>
</c:forEach>
</div>
<%@ include file="layout/footer.jsp"%>
흐름 정리
1. "/" 요청
2. boardService.글목록() 으로 글 전부를 다 긁어온다.
3. 긁어온 정보를 "boards"라는 이름으로 index에 넘긴다.
4. index는 {boards}를 받아서 뿌려준다.
글목록 페이징하기
예전에 페이징을 위해 @PageableDefault를 사용해봤었는데 똑같이 적용한다.
BoardController.java
@Controller
public class BoardController {
@Autowired
private BoardService boardService;
@GetMapping({"", "/"})
public String index(Model model, @PageableDefault(size=3, sort="id", direction = Sort.Direction.DESC) Pageable pageable) {
model.addAttribute("boards", boardService.글목록(pageable));
return "index";
}
...
}
BoardService.java
@Service
public class BoardService {
...
@Transactional(readOnly = true)
public Page<Board> 글목록(Pageable pageable) {
return boardRepository.findAll(pageable);
}
}
index.js
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ include file="layout/header.jsp"%>
<div class="container">
<c:forEach var="board" items="${boards.content}">
<div class="card m-2">
<div class="card-body">
<h4 class="card-title">${board.title }</h4>
<a href="#" class="btn btn-primary">상세 보기</a>
</div>
</div>
</c:forEach>
<ul class="pagination justify-content-center"> <!-- 가운데로 보내기 -->
<c:choose>
<c:when test="${boards.first}">
<li class="page-item disabled"><a class="page-link" href="?page=${boards.number-1 }">Previous</a></li>
</c:when>
<c:otherwise>
<li class="page-item"><a class="page-link" href="?page=${boards.number-1 }">Previous</a></li>
</c:otherwise>
</c:choose>
<c:choose>
<c:when test="${boards.last}">
<li class="page-item disabled"><a class="page-link" href="?page=${boards.number+1 }">Next</a></li>
</c:when>
<c:otherwise>
<li class="page-item"><a class="page-link" href="?page=${boards.number+1 }">Next</a></li>
</c:otherwise>
</c:choose>
</ul>
</div>
<%@ include file="layout/footer.jsp"%>
글 상세보기
상세보기를 누르면 http://localhost:8000/board/{글의 id값} 링크로 이동하도록 수정
index.js
...
<div class="container">
<c:forEach var="board" items="${boards.content}">
<div class="card m-2">
<div class="card-body">
<h4 class="card-title">${board.title }</h4>
<a href="/board/${board.id}" class="btn btn-primary">상세 보기</a>
</div>
</div>
</c:forEach>
...
</div>
...
상세보기 링크로 이동하면 컨트롤러에서 id값을 매개변수로 boardService의 글 상세보기를 실행해서 리턴 받은 값을 "board"라는 이름으로 board/detail에 넘겨준다.
BoardController.java
@Controller
public class BoardController {
...
@GetMapping("/board/{id}")
public String findById(@PathVariable int id, Model model) {
model.addAttribute("board", boardService.글상세보기(id));
return "board/detail";
}
...
}
BoardService.java
@Service
public class BoardService {
...
@Transactional(readOnly = true)
public Board 글상세보기(int id) {
return boardRepository.findById(id)
.orElseThrow(()->{
return new IllegalArgumentException("글 상세보기 실패 : 아이디를 찾을 수 없습니다.");
});
}
}
전달받은 "board"로 title과 content 정보를 뿌려준다.
detail.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ include file="../layout/header.jsp"%>
<div class="container">
<button class="btn btn-secondary" onclick="history.back()">돌아가기</button>
<button id="btn-update" class="btn btn-warning">수정</button>
<button id="btn-delete" class="btn btn-danger">삭제</button>
<br/><br/>
<div>
<h3>${board.title}</h3>
</div>
<hr />
<div>
<div>${board.content}</div>
</div>
<hr />
</div>
<script src="/js/board.js"></script>
<%@ include file="../layout/footer.jsp"%>
글 삭제하기
일단 상세보기에서 글 번호와 작성자를 보여주자. board는 EAGER로 user 정보를 다 들고오니까 board.user.username으로 작성자 정보를 가져올 수 있다.
detail.jsp
...
<div class="container">
...
<c:if test="${board.user.id == principal.user.id }">
<button id="btn-update" class="btn btn-warning">수정</button>
<button id="btn-delete" class="btn btn-danger">삭제</button>
</c:if>
<br /><br />
<div>
글 번호 : <span id="id"><i>${board.id} </i></span>
작성자 : <span><i>${board.user.username} </i></span>
</div>
...
</div>
...
삭제를 위해서는 id값이 필요하다. $("#id").text(); 로 id 값을 찾아온 후 url에 붙여준다.
board.js
let index = {
init: function() {
...
$("#btn-delete").on("click", () => {
this.deleteById();
});
},
...
deleteById: function() {
var id = $("#id").text();
$.ajax({
type: "DELETE",
url: "/api/board/"+id,
dataType: "json"
}).done(function(resp) {
alert("삭제가 완료되었습니다.")
location.href = "/";
}).fail(function(error) {
alert(JSON.stringify(error));
});
},
}
index.init();
BoardApiController에서 @DeleteMapping으로 "/api/board/{id}" url이 매핑되면 id를 추출해서 삭제시킨다.
BoardApiController.java
@RestController
public class BoardApiController {
...
@DeleteMapping("/api/board/{id}")
public ResponseDto<Integer> deleteById(@PathVariable int id){
boardService.글삭제하기(id);
return new ResponseDto<Integer>(HttpStatus.OK.value(), 1);
}
}
BoardService.java
public class BoardService {
...
@Transactional
public void 글삭제하기(int id) {
boardRepository.deleteById(id);
}
}
글 수정하기
수정 버튼을 누르면 각 글의 updateForm으로 이동해서 수정할 수 있도록 한다.
detail.jsp
<a href="/board/${board.id}/updateForm" class="btn btn-warning">수정</button>
BoardController.java
@GetMapping("/board/{id}/updateForm")
public String updateForm(@PathVariable int id, Model model) {
model.addAttribute("board", boardService.글상세보기(id));
return "board/updateForm";
}
수정을 하기 위해선 title, content 값 뿐만 아니라 id값도 있어야 하므로 hidden 타입으로 적어둔다.
board/updateForm
...
<div class="container">
<form>
<input type="hidden" id="id" value="${board.id}"/>
<div class="form-group">
<input value="${board.title}" type="text" class="form-control" placeholder="Enter title" id="title">
</div>
<div class="form-group">
<textarea class="form-control summernote" rows="5" id="content">${board.content}</textarea>
</div>
</form>
<button id="btn-update" class="btn btn-primary">글수정완료</button>
</div>
...
입력된 id, title, content 값을 들고와서 Ajax를 작동시킨다.
js/board.js
let index = {
init: function() {
...
$("#btn-update").on("click", () => {
this.update();
});
},
...
update: function() {
let id = $("#id").val();
let data = {
title: $("#title").val(),
content: $("#content").val()
};
$.ajax({
type: "PUT",
url: "/api/board/"+id,
data: JSON.stringify(data),
contentType: "application/json; charset=utf-8",
dataType: "json"
}).done(function(resp) {
alert("글수정이 완료되었습니다.");
location.href = "/";
}).fail(function(error) {
alert(JSON.stringify(error));
});
},
}
index.init();
BoardApiController.java
@PutMapping("/api/board/{id}")
public ResponseDto<Integer> update(@PathVariable int id, @RequestBody Board board) {
boardService.글수정하기(id, board);
return new ResponseDto<Integer>(HttpStatus.OK.value(), 1);
}
BoardService.java
@Transactional
public void 글수정하기(int id, Board requestBoard) {
Board board = boardRepository.findById(id)
.orElseThrow(()->{
return new IllegalArgumentException("글 찾기 실패 : 아이디를 찾을 수 없습니다.");
}); // 영속화 완료
board.setTitle(requestBoard.getTitle());
board.setContent(requestBoard.getContent());
// 해당 함수로 종료시에(Service가 종료될 때) 트랜잭션이 종료된다. 이때 더티체킹이 들어간다. -> 자동 업데이트. DB flush!!
}
최종 정리
출처 : https://www.youtube.com/c/%EB%A9%94%ED%83%80%EC%BD%94%EB%94%A9
'Spring > Blog 만들기 with SpringBoot' 카테고리의 다른 글
카카오 로그인 구현 (0) | 2022.08.21 |
---|---|
회원 수정 (0) | 2022.07.25 |
비밀번호 해쉬 & 시큐리티 로그인 (0) | 2022.07.24 |
스프링부트 Spring Security (0) | 2022.07.14 |
전통적인 방식의 로그인 (0) | 2022.07.09 |