JPA로 DB Delete 수행하기
✒️ 2025-05-28 14:04 내용 수정
스프링부트3 자바 백엔드 개발입문 내용 참고 및 정리
JPA로 DB Delete 수행하기
- 이전에 Spring boot 수업에선 MyBatis를 사용하여 CRUD를 진행했다. 이번에는 참고서의 내용으로 JPA를 사용하여 H2 DB에 CRUD를 수행하는 실습을 진행했다.
- 템플릿 엔진도 Thymeleaf 대신 Mustache를 사용했다.
- JPA로 DB Create 수행하기, JPA로 DB Read 수행하기, JPA로 DB Update 수행하기에 이어 이번엔 데이터를 삭제하는 과정을 실습했다.
데이터 삭제하기
- 글을 삭제하는 버튼을 추가하기 위해
show.mustache파일에 먼저 버튼을 추가한다.- 여기서 삭제 버튼을 눌렀을 때
confirm()창을 띄워 재확인하는 동작을 추가하기 위해 버튼에onclick이벤트 리스너를 추가했다.
- 여기서 삭제 버튼을 눌렀을 때
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<title>SpringBoot Example</title>
</head>
<body>
{{> layouts/header }}
<section>
<div class="inner p-5">
<h1>Article #{{article.id}}</h1>
<table class="table">
<thead>
<tr>
<th scope="col">Id</th>
<th scope="col">Title</th>
<th scope="col">Content</th>
</tr>
</thead>
<tbody>
{{#article}}
<tr>
<th scope="row">{{id}}</th>
<td>{{title}}</td>
<td>{{content}}</td>
</tr>
{{/article}}
</tbody>
</table>
<button type="button" class="btn btn-primary" onclick="location.href='/articles/{{article.id}}/edit'">Edit</button>
<button type="button" class="btn btn-danger" id="delete-btn" onclick="check()">Delete</button>
<button type="button" class="btn btn-secondary" onclick="location.href='/articles/'">Back</button>
</div>
</section>
{{> layouts/footer }}
<script>
function check() { // 삭제 확인용
if (confirm("삭제하시겠습니까?") != false) {
location.href='/articles/{{article.id}}/delete';
}
}
</script>
</body>
</html>
ArticleController에서 삭제 동작을 위한@GetMapping을 추가한다.Get방식으로 요청받아 삭제 동작을 진행하였다.- 교재에서
@DeleteMapping을 사용하는 것은 REST API 컨트롤러 사용과 함께 진행되기에 실습을 진행할 때도 같은 방식으로 진행했다. RedirectAttributes객체를 사용하면 리다이렉트 페이지에서 사용할 데이터를 남길 수 있다.addFlashAttribute()메소드를 사용하면 리다이렉트 시점에서 한 번만 사용할 수 있는 데이터를 추가할 수 있어 이를 이용해 삭제 완료 메시지를 남길 수 있다.
package com.example.demo.controller;
import com.example.demo.DTO.ArticleForm;
import com.example.demo.entity.Article;
import com.example.demo.repository.ArticleRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
@Controller
@RequestMapping("/articles")
@Slf4j // simple logging facade for java
public class ArticleController {
@Autowired
private ArticleRepository articleRepository;
@GetMapping("/") // 데이터 리스트 출력
public String list(Model model) {
List<Article> list = (List<Article>) articleRepository.findAll();
model.addAttribute("articles", list);
return "/articles/list";
}
@GetMapping("new")
public String newArticleForm() {
return "/articles/new";
}
@PostMapping("create")
public String createArticle(ArticleForm form) {
Article article = form.toEntity();
Article saved = articleRepository.save(article);
return "redirect:/articles/" + saved.getId(); // 글을 추가하면 해당 글 상세보기로 이동
}
@GetMapping("{id}") // 경로 매개변수 사용
public String show(@PathVariable Long id, Model model) {
log.info("id = " + id); // 로그 확인
Article articleEntity = articleRepository.findById(id).orElse(null); // id로 조회
model.addAttribute("article", articleEntity);
return "/articles/show"; // id로 조회할 데이터를 보여줄 view
}
@GetMapping("{id}/edit") // 수정 페이지 이동
public String edit(@PathVariable Long id, Model model) {
Article articleEntity = articleRepository.findById(id).orElse(null); // id로 조회
model.addAttribute("article", articleEntity); // view에 조회한 데이터 전달
return "/articles/edit"; // id로 조회할 데이터를 수정할 페이지 view
}
@PostMapping("update") // 수정 동작
public String update(ArticleForm form) {
Long id = form.getId(); // form으로 같이 온 id
Article article = form.toEntity(); // DTO to entity
Article target = articleRepository.findById(id).orElse(null); // 수정 대상
if (target != null) { // 조회한 데이터가 존재할 때만 수정동작 실행
articleRepository.save(article);
}
return "redirect:/articles/" + id; // 수정이 완료되면 해당 글의 상세보기로 리다이렉트
}
@GetMapping("{id}/delete") // 삭제 동작
public String delete(@PathVariable Long id, RedirectAttributes rttr) {
Article target = articleRepository.findById(id).orElse(null); // 삭제 대상
if (target != null) { // 조회한 데이터가 존재할 때만 삭제동작 실행
articleRepository.delete(target);
rttr.addFlashAttribute("msg", "삭제되었습니다."); // 리다이렉트할 페이지에 남길 데이터
}
return "redirect:/articles/";
}
}
- 삭제 후 리다이렉트될
list.mustache페이지에서 삭제가 완료되었다는 메시지를 추가해준다.- 리다이렉트로 넘어올 값이 존재할 때만 메시지 창을 띄워야하기 때문에 mustache의
{{#변수명}}{{/변수명}}문법을 사용한다. - bootstrap의 alert 창을 사용했다.
- Bootstrap Alerts
- 리다이렉트로 넘어올 값이 존재할 때만 메시지 창을 띄워야하기 때문에 mustache의
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<title>SpringBoot Example</title>
</head>
<body>
{{> layouts/header }}
<section>
<div class="inner p-5">
<h1>Articles</h1>
{{#msg}}
<div class="alert alert-success d-flex align-items-center" role="alert">
<div>{{msg}}</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{{/msg}}
<table class="table">
<thead>
<tr>
<th scope="col">Id</th>
<th scope="col">Title</th>
<th scope="col">Content</th>
</tr>
</thead>
<tbody> {{#articles}}
<tr>
<th scope="row">{{id}}</th>
<td><a href="/articles/{{id}}">{{title}}</a></td>
<td>{{content}}</td>
</tr>
{{/articles}}
</tbody>
</table>
<button type="button" class="btn btn-primary" onclick="location.href='/articles/new'">Write Article</button>
</div>
</section>
{{> layouts/footer }}
</body>
</html>
- 먼저 제거를 위해 새 데이터를 추가하고, 해당 글 상세보기 페이지에 있는 삭제 버튼을 누른다.
- 삭제를 재확인하는
confirm()창이 뜨면 확인을 눌러 삭제를 진행한다.
- 삭제가 완료되면 자동으로 목록 페이지로 리다이렉트되며 삭제되었다는 메시지와 함께 화면에서 해당 글도 보이지 않는다.