JPA로 DB Delete 수행하기

✒️ 2025-05-28 14:04 내용 수정

스프링부트3 자바 백엔드 개발입문 내용 참고 및 정리


JPA로 DB Delete 수행하기


데이터 삭제하기

  1. 글을 삭제하는 버튼을 추가하기 위해 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>
  1. 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/";  
    }  
}
  1. 삭제 후 리다이렉트될 list.mustache 페이지에서 삭제가 완료되었다는 메시지를 추가해준다.
    • 리다이렉트로 넘어올 값이 존재할 때만 메시지 창을 띄워야하기 때문에 mustache의 {{#변수명}}{{/변수명}} 문법을 사용한다.
    • bootstrap의 alert 창을 사용했다.
    • Bootstrap Alerts
<!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>

jpa_delete 1.png

jpa_delete 2.png

jpa_delete 3.png