[트러블 슈팅] 9. 게시판 삭제가 왜 안됐을까?
Mar 18, 2025
Contents
문제문제
package com.metacoding.blogv1.board;
import jakarta.transaction.Transactional;
import org.springframework.stereotype.Service;
import java.util.List;
// 책임 : 트랜잭션 처리, 비지니스 로직 처리 (ex. 잔액 검사)
@Service // IOC
public class BoardService {
    private BoardRepository boardRepository;
    // DI : 의존성 주임 -> IOC로 부터 들고옴
    public BoardService(BoardRepository boardRepository) {
        this.boardRepository = boardRepository;
    }
    @Transactional // 트랜잭션 시작 -> 함수 내부가 다 수행되면 commit, 실패하면 rollback
    public void 게시글쓰기(String title, String content) {
        boardRepository.insert(title, content);
    }
    public List<Board> 게시글목록() {
        List<Board> boardList = boardRepository.findAll();
        return boardList;
    }
    public Board 게시글상세보기(int id) {
        return boardRepository.findById(id);
    }
    
    public void 게시글삭제(int id) {
        // 1. 게시글이 존재하는지 확인
        Board board = boardRepository.findById(id);
        // 2. 삭제
        if (board == null) {
            throw new RuntimeException("게시글이 없는데 왜 삭제해");
        }
        boardRepository.deleteById(id);
    } // commit
}
package com.metacoding.blogv1.board;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.List;
// 책임 : 요청 잘받고, 응답 잘하고
@Controller // 컴퍼넌트 스캔 -> DS가 활용
public class BoardController {
    private BoardService boardService;
    public BoardController(BoardService boardService) {
        this.boardService = boardService;
    }
    @PostMapping("/board/{id}/update")
    public String update(@PathVariable("id") int id, String title, String content) {
        // update boar_tb set title=?, content=? where id = ?
        // 주소로 받는 데이터는 전부 다 where에 걸린다.
        System.out.println("id : " + id);
        System.out.println("title : " + title);
        System.out.println("content : " + content);
        return "redirect:/board/" + id;
    }
    @PostMapping("/board/{id}/delete")
    public String delete(@PathVariable("id") int id) {
        boardService.게시글삭제(id);
        return "redirect:/";
    }
    @PostMapping("/board/save") // action / ~ 수행해줘
    public String save(String title, String content) {
        System.out.println("title: " + title + " content: " + content);
        boardService.게시글쓰기(title, content);
        return "redirect:/"; // 주소가 만들어져있으면 무조건 리다이렉션
    }
    // Controller -> Model -> View : MVC 패턴
    @GetMapping("/")
    public String list(HttpServletRequest request) {
        List<Board> boardList = boardService.게시글목록();
        request.setAttribute("models", boardList); // Request 담기
        return "list"; // forward
    }
    @GetMapping("/board/{id}") // 패턴 매칭 /board/1,2,3,4
    public String detail(@PathVariable("id") int id, HttpServletRequest request) {
        Board board = boardService.게시글상세보기(id);
        request.setAttribute("model", board);
        return "detail";
    }
    @GetMapping("/board/save-form") // 주소는 하이픈(-) 사용
    public String saveForm() {
        return "save-form";
    }
    @GetMapping("/board/{id}/update-form") // URI 주소
    public String updateForm(@PathVariable("id") int id) {
        return "update-form"; // 파일명
    }
}package com.metacoding.blogv1.board;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
// 책임 : DB와 소통하는 친구
@Repository // IOC 컬렉션(저장소)에 뜬다.
public class BoardRepository {
    private EntityManager em;
    // DI -> IOC 순회해서 타입으로 찾아서 전달해준다.
    public BoardRepository(EntityManager em) {
        System.out.println("BoardRepository new 됨");
        this.em = em;
    }
    // 나중에 쿼리, 셀렉트 필요하면 여기에 만들면 됨
    public void insert(String title, String content) {
        Query query = em.createNativeQuery("insert into board_tb(title, content, created_at) values(?,?,now())");
        query.setParameter(1, title);
        query.setParameter(2, content);
        query.executeUpdate(); // insert, update, delete
    }
    public List<Board> findAll() {
        Query query = em.createNativeQuery("select * from board_tb order by id desc", Board.class);
        List<Board> boardList = query.getResultList(); // 여러 건 조회
        return boardList;
    }
    public Board findById(int id) {
        Query query = em.createNativeQuery("select * from board_tb where id = ?", Board.class);
        query.setParameter(1, id);
        try {
            Board board = (Board) query.getSingleResult(); // 한 건 조회
            return board;
        } catch (Exception e) {
            return null;
        }
    }
    public void deleteById(int id) {
        Query query = em.createNativeQuery("delete from board_tb where id = ?");
        query.setParameter(1, id);
        query.executeUpdate();
    }
}{{> layout/header}}
<section>
    <a href="/board/{{model.id}}/update-form">수정화면가기</a>
    <form action="/board/{{model.id}}/delete" method="post">
        <button type="submit">삭제</button>
    </form>
    <div>
        번호 : {{model.id}} <br>
        제목 : {{model.title}} <br>
        내용 : {{model.content}} <br>
        작성일 : {{model.createdAt}} <br>
    </div>
</section>
</body>
</html><!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>blog</title>
</head>
<body>
<nav>
    <ul>
        <li>
            <a href="/">홈</a>
        </li>
        <li>
            <a href="/board/save-form">글쓰기</a>
        </li>
    </ul>
</nav>
<hr>{{> layout/header}}
<section>
    <table border="1">
        <tr>
            <th>번호</th>
            <th>제목</th>
            <th></th>
        </tr>
        {{#models}}
            <tr>
                <td>{{id}}</td>
                <td>{{title}}</td>
                <td><a href="/board/{{id}}">상세보기</a></td>
            </tr>
        {{/models}}
    </table>
</section>
</body>
</html>{{> layout/header}}
<section>
    <!--    http body : title=제목6&content=내용6
            http header : application/x-www-form-urlencoded
            key값은 input태그의 name, value값은 input태그에 사용자가 입력하는 값-->
    <form action="/board/save" method="post" enctype="application/x-www-form-urlencoded">
        <input type="text" name="title" placeholder="제목"><br>
        <input type="text" name="content" placeholder="내용"><br>
        <button type="submit">글쓰기</button>
    </form>
</section>
</body>
</html>{{> layout/header}}
<section>
    <form action="/board/1/update" method="post" enctype="application/x-www-form-urlencoded">
        <input type="text" name="title" value="제목1"><br>
        <input type="text" name="content" value="내용1"><br>
        <button type="submit">글수정</button>
    </form>
</section>
</body>
</html># utf-8 한글 인코딩
server.servlet.encoding.charset=utf-8
server.servlet.encoding.force=true
# DB 연결 코드 (EntityManager 만들어냄)
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:test
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
# JPA @Entity 스캔해서 테이블 생성
spring.jpa.hibernate.ddl-auto=create
# 콘솔에 쿼리 표시
spring.jpa.show-sql=true
# 더미데이터 sql문 실행
spring.sql.init.data-locations=classpath:db/data.sql
# ddl-auto가 실행된 후에 sql문 실행하는 법
spring.jpa.defer-datasource-initialization=true
# mustache에서 request 객체 접근하게 설정하는 법
spring.mustache.servlet.expose-request-attributes=true
해결법
package com.metacoding.blogv1.board;
import jakarta.transaction.Transactional;
import org.springframework.stereotype.Service;
import java.util.List;
// 책임 : 트랜잭션 처리, 비지니스 로직 처리 (ex. 잔액 검사)
@Service // IOC
public class BoardService {
    private BoardRepository boardRepository;
    // DI : 의존성 주임 -> IOC로 부터 들고옴
    public BoardService(BoardRepository boardRepository) {
        this.boardRepository = boardRepository;
    }
    @Transactional // 트랜잭션 시작 -> 함수 내부가 다 수행되면 commit, 실패하면 rollback
    public void 게시글쓰기(String title, String content) {
        boardRepository.insert(title, content);
    }
    public List<Board> 게시글목록() {
        List<Board> boardList = boardRepository.findAll();
        return boardList;
    }
    public Board 게시글상세보기(int id) {
        return boardRepository.findById(id);
    }
    @Transactional
    public void 게시글삭제(int id) {
        // 1. 게시글이 존재하는지 확인
        Board board = boardRepository.findById(id);
        // 2. 삭제
        if (board == null) {
            throw new RuntimeException("게시글이 없는데 왜 삭제해");
        }
        boardRepository.deleteById(id);
    } // commit
}

BoardService.java 코드에서 @Transactional을 붙이지 않아서 삭제가 되지 않았던 것 이었다.

Share article