안녕하세요 코이킹입니다.
이번 포스트는 글 삭제 기능을 구현하는 과정에 대한 내용이 되겠습니다.
1. 목표
- 스프링 부트를 사용해서 웹 어플리케이션을 만드는 흐름에 대해서 이해할 수 있다.
- 스프링 부트를 사용해서 기존 데이터를 삭제할 수 있는 웹 어플리케이션을 만들 수 있다.
- 스프링 부트를 사용해서 기본적인 CRUD 기능의 웹 어플리케이션을 만들 수 있다.
2. 어떻게 구현할지에 대한 설명
글 삭제 기능의 핵심기능을 생각해보면 '기존의 특정한 데이터를 삭제하는 것'입니다.
핵심기능을 바탕으로 글 삭제 기능을 구현하기 위해 해야할 일을 추려보면 다음과 같습니다.
스스로 질문해 보기 | 스스로 추려낸 답변 |
삭제대상 데이터를 유저에게 표시할 것인가? | 삭제할 데이터를 페이나 모달로 표시하고, 유저가 확인 버튼을 클릭했을때 삭제하도록 하자 |
삭제대상 데이터를 유저에게 어떻게 표시할것 인가? | 삭제확인페이지를 만들어서 표시하자 |
글 삭제페이지는 어떻게 이동해야할까? | 글 상세보기에서 이동링크를 추가하자 |
특정 데이터를 가져오기 위한 키값을 어떻게 백엔드로 보낼 수 있을까? | 요청 파라미터나 경로 파라미터에 특정 값을 담아서 요청하거나, POST요청이라면, 요청 바디에 특정 값을 담아서 넘길 수 있겠다. |
글 삭제확인 페이지 표시요청과, 글 삭제처리 요청은 백엔드에서 어떻게 수신해야할까? | 컨트롤러에 글 삭제확인 페이지 표시 요청과 글 삭제처리 요청을 각각 매칭하는 메서드를 추가한다. |
위의 해야할 일을 보면 글 수정 기능을 구현할 때의 내용과 거의 동일한 것을 알 수 있습니다.
이번에도 이미 구현해둔 글 수정기능을 참조하여 글 삭제 기능을 쉽게 구현이 가능할 겁니다.
3. 소스코드와 해석
1) 글 상세보기 템플릿 : /template-springboot/src/main/resources/templates/board/read.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, inital-scale=1.0"/>
</head>
<body>
<div>
<article class="blog-post">
<div class="board-read-header">
<h1 class="blog-post-title">[[${board.title}]]</h1>
<hr/>
<p class="blog-post-meta mt-2">Created at [[${board.createdTime}]] / Last Updated : [[${board.updatedTime}]]</p>
</div>
<hr/>
<div class="mt-4 mb-5 px-2 py-2" style="width: 100%;">
<div th:text="${board.contents}"></div>
</div>
</article>
<div>
<button class="btn btn-info" type="button" onclick="location.href='/board/list'">Back to List</button>
<button class="btn btn-warning" type="button" th:onclick="'location.href=\'' + @{/board/update/{boardNo}(boardNo=${board.boardNo})} +'\';'">Update</button>
<button class="btn btn-danger" type="button" th:onclick="'location.href=\'' + @{/board/delete/{boardNo}(boardNo=${board.boardNo})} +'\';'">Delete</button>
</div>
</div>
</body>
</html>
-28행 : 글 삭제확인 페이지로 이동하는 링크를 추가했습니다.
글 수정기능 구현 시의 링크를 참조하여 작성했습니다.
2) 글 삭제확인 템플릿 : /template-springboot/src/main/resources/templates/board/delete.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, inital-scale=1.0"/>
<title>Board Write</title>
</head>
<body>
<div>
<form method="post" th:action=@{/board/delete/}>
<input type="hidden" name="boardNo" th:value=${board.boardNo}>
<fieldset>
<legend class="text-center header">Delete Board</legend>
<div class="board-read-header">
<h5><label> Do you Really want to delete contents ?? </label></h5><br/>
<h5><label>【Title】 : [[${board.title}]] </label></h5>
</div>
<hr/>
<div class="form-group py-2">
<input type="submit" value="Delete Complete" class="btn btn-danger">
</div>
</fieldset>
</form>
<div>
<button class="btn btn-info py-2 my-2" type="button" onclick="location.href='/board/list'">Back to List</button>
<button class="btn btn-warning py-2 my-2" type="button" th:onclick="'location.href=\'' + @{/board/read/{boardNo}(boardNo=${board.boardNo})} +'\';'">Back to [[${board.boardNo}]]</button>
</div>
</div>
</body>
</html>
- 13행 : 글 수정 처리 요청 시에 사용한 코드를 변경하여, 글 삭제처리 요청을 하도록 코드를 수정했습니다.
3) 컨트롤러 : /template-springboot/src/main/java/com/sb/template/controller/BoardController.java
package com.sb.template.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import com.sb.template.entity.Board;
import com.sb.template.enums.BoardType;
import com.sb.template.forms.BoardForm;
import com.sb.template.service.BoardService;
@Controller
@RequestMapping(path = "/board")
public class BoardController {
@Autowired
private BoardService boardService;
@RequestMapping(method = RequestMethod.GET, path = "list")
public String viewBoardList(Model model) {
List<Board> boardList = boardService.getAllBoard();
model.addAttribute("boardList", boardList);
return "board/list";
}
@RequestMapping(method = RequestMethod.GET, path = "write")
public String writeBoard(Model model) {
model.addAttribute("boardTypes", BoardType.getBoardTypes());
return "board/write";
}
@RequestMapping(method = RequestMethod.POST, path = "write")
public String writeCompleteBoard(BoardForm form, Model model) {
Integer boardNo = null;
boardNo = boardService.createBoard(form.toEntity()).getBoardNo();
return "redirect:/board/list";
}
@RequestMapping(method = RequestMethod.GET, path = "read/{boardNo}")
public String viewBoardOne(@PathVariable int boardNo, Model model) {
model.addAttribute("board", boardService.getBoardOne(boardNo));
return "board/read";
}
@RequestMapping(method = RequestMethod.GET, path = "update/{boardNo}")
public String updateBoard(@PathVariable int boardNo, Model model) {
boardService.updateBoardForm(boardNo, model);
return "board/update";
}
@RequestMapping(method = RequestMethod.POST, path = "update/{boardNo}")
public String updateCompleteBoard(@PathVariable int boardNo, BoardForm form, Model model) {
boardService.updateBoard(boardNo, form.toEntity());
model.addAttribute("message", "Update Success");
return "redirect:/board/read/"+boardNo;
}
@RequestMapping(method = RequestMethod.GET, path = "delete/{boardNo}")
public String deleteBoard(@PathVariable int boardNo, Model model) {
model.addAttribute("board", boardService.getBoardOne(boardNo));
return "board/delete";
}
@RequestMapping(method = RequestMethod.POST, path = "delete")
public String deleteCompleteBoard(
@RequestParam(name = "boardNo", required = true) int boardNo,
Model model) {
boardService.deleteBoard(boardNo);
model.addAttribute("message", "Delete Success");
return "redirect:/board/list";
}
}
- 83행 : 글 상세보기 구현에서처럼 '/{파라미터명}'으로 path의 값을 설정했습니다.
- 86행 : 글 삭제확인 페이지에 표시할 데이터를 설정하는 처리를 서비스에 위임했습니다.
글번호로 특정 게시글의 데이터를 가져오는 메서드는 글 상세보기에서 이미 구현해두었으므로 그대로 사용합니다.
- 94행 : url에 데이터를 붙여서 보내는 요청 파라미터로 삭제할 게시글의 글 번호를 받고 있습니다.
@RequestParam어노테이션의 required 속성의 값을 true로 설정하면, 삭제처리 요청 시에 반드시 글 번호를 요청 파라미터로 받아야만, 정상적으로 요청 처리가 진행됩니다.
- 97행 : 글 삭제 처리를 서비스에 위임했습니다.
- 100행 : 글 목록으로 이동하여 데이터를 확인 합니다.
4) 서비스 : /template-springboot/src/main/java/com/sb/template/service/BoardService.java
package com.sb.template.service;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import com.sb.template.entity.Board;
import com.sb.template.enums.BoardType;
import com.sb.template.repo.BoardRepository;
@Service
public class BoardService {
@Autowired
private BoardRepository boardRepository;
public List<Board> getAllBoard() {
List<Board> res = boardRepository.findAll();
if (res == null) return null;
return res;
}
public Board createBoard(Board board) {
return boardRepository.save(board);
}
public Board getBoardOne(int boardNo) {
Optional<Board> board = boardRepository.findById(boardNo);
if (board.isEmpty()) {
return null;
}
return board.get();
}
public void updateBoardForm(int boardNo, Model model) {
Optional<Board> board = boardRepository.findById(boardNo);
if (board.isEmpty()) {
return ;
}
model.addAttribute("boardTypes", BoardType.getBoardTypes());
model.addAttribute("board", board.get());
}
@Transactional
public Board updateBoard(int boardNo, Board updatedBoard) {
Optional<Board> res = boardRepository.findById(boardNo);
if (res.isEmpty()) {
return null;
}
Board board = res.get();
board.setType(updatedBoard.getType());
board.setTitle(updatedBoard.getTitle());
board.setContents(updatedBoard.getContents());
Board endUpdatedBoard = boardRepository.save(board);
return endUpdatedBoard;
}
public void deleteBoard(int boardNo) {
Optional<Board> res = boardRepository.findById(boardNo);
if (res.isEmpty()) {
return ;
}
boardRepository.delete(res.get());
}
}
- 74~83행 : 게시글 삭제를 처리하는 메서드입니다.
findById메서드를 실행하여 삭제하려는 게시글 데이터가 존재하는지를 확인한 후 존재한다면, 게시글 데이터를 삭제합니다.
4. 동작확인
5. 데이터 흐름
① 글 상세보기 페이지에서 글 삭제 버튼을 클릭하면, 글 삭제확인 페이지 요청을 백엔드로 보내게 된다.
요청을 보낼때 경로 파라미터로 글 번호를 같이 보내, 삭제할 글의 데이터를 가져올 키 값으로 사용한다.
② DB에서 삭제할 게시글의 데이터를 가져와 요청 스코프에 담아서, 글 삭제 확인 템플릿을 브라우저에 리턴한다.
③ 글 삭제 확인버튼을 클릭하면, 글 삭제 처리 요청을 백엔드로 보내게 된다.
요청 시에는 요청 파라미터에 삭제할 게시글의 글 번호를 넣어서 보낸다.
서비스의 메서드를 호출할 때에는 삭제할 게시글 번호를 파라미터로 넘기며, 게시글 번호를 키로 삭제할 데이터가 존재하는지를 먼저 확인한다.
④ 삭제할 데이터가 존재하는 경우 글 삭제처리가 실행된다.
⑤ 삭제가 완료된 후 글 목록 페이지를 리다이렉트 URL로 설정한 후 요청에 대한 응답을 브라우저로 보낸다.
⑥ 브라우져에서 글 목록 페이지로 리다이렉트 요청을 보낸다.
⑦ 백엔드에서 글 목록 요청에 해당하는 응답을 브라우저로 리턴한다.
글 삭제 구현은 이것으로 마치겠습니다.
글 목록에서 부터 글삭제까지를 기록한 포스트의 코드를 이해하시고, 화면에서 컨트롤러, 서비스, 리포지토리를 통해 DB까지 데이터가 이동하고, 다시 역순으로 DB에서 부터 화면까지 데이터가 이동하는 일련의 흐름을 이해하신다면
CRUD기능을 응용한 웹 어플리케이션은 만들어 내실 수 있다고 생각합니다.
다음 포스트의 주제는 웹 어플리케이션에 필수적인 기능 설정 몇 가지에 대한 설명과 Log4j설정이 되겠습니다.
'프로그래밍 > Springboot-토이프로젝트' 카테고리의 다른 글
【게시판-번외04】AOP를 적용한 로그출력 (0) | 2022.09.14 |
---|---|
【게시판-번외03】필수기능 설정 & Log4j설정 (0) | 2022.09.14 |
【게시판-05】글 수정 (0) | 2022.09.11 |
【게시판-04】글상세보기 (0) | 2022.09.09 |
【게시판-03】글작성 (1) | 2022.09.08 |
댓글