본문 바로가기
프로그래밍/Springboot-토이프로젝트

【게시판-06】글 삭제

by 코이킹 2022. 9. 13.
반응형

안녕하세요 코이킹입니다.
이번 포스트는 글 삭제 기능을 구현하는 과정에 대한 내용이 되겠습니다. 


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설정이 되겠습니다.

 

반응형

댓글