본문 바로가기
Spring Boot

스프링 부트(Spring Boot) 게시판 - 게시글 삭제 구현하기 [Thymeleaf, MariaDB, IntelliJ, Gradle, MyBatis]

by 도뎡 2023. 4. 5.
반응형

  • 본 게시판 프로젝트는 단계별(step by step)로 진행되니, 이전 단계를 진행하시는 것을 권장드립니다.
  • DBMS 툴은 DBeaver를 이용하며, DB는 MariaDB를 이용합니다. (MariaDB 설치하기)
  • 화면 처리는 HTML5 기반의 자바 템플릿 엔진인 타임리프(Thymeleaf)를 사용합니다.

이전 글에서는 게시글 상세 페이지와 게시글 수정 기능을 구현해 보았습니다. 이번에는 특정 게시글을 삭제하는 기능을 구현해 볼 건데요. 리스트, 상세 페이지와 마찬가지로 컨트롤러와 HTML만 조금 손봐주면 게시글 삭제 기능 구현이 완료됩니다.

 

1. 컨트롤러(Controller)에 메서드 추가하기

PostController에 다음의 메서드를 추가해 주세요.

    // 게시글 삭제
    @PostMapping("/post/delete.do")
    public String deletePost(@RequestParam final Long id) {
        postService.deletePost(id);
        return "redirect:/post/list.do";
    }

 

전체 로직

게시글 번호(id)를 파라미터로 전달받아 특정 게시글을 삭제합니다. 여기서 삭제는 테이블상에서의 물리적인 DELETE가 아닌, 삭제 여부(delete_yn) 칼럼의 상태 값을 변경하는 논리 삭제입니다. 게시글이 삭제된 후에는 리스트 페이지로 리다이렉트 합니다.

 

2. 화면(HTML) 수정하기

게시글 삭제는 상세 페이지(view.html)에서 이루어지며, 삭제하기 버튼에 onclick 이벤트를 바인딩해서 게시글을 삭제 처리합니다. 우선, 삭제버튼을 클릭했을 때 deletePost( ) 함수가 실행되도록 onclick 이벤트를 선언해 주세요.

게시글 상세 페이지 버튼 엘리먼트

 

 

백엔드 영역의 구성은 모두 끝났으니, view.html에 deletePost( ) 함수를 선언하고 PostController의 deletePost( )를 호출해 주기만 하면 삭제 처리는 끝입니다.

 

view.html에 JS 영역의 코드를 작성해 주시면 되는데요. 여러분의 혼란을 방지하고자 view.html의 전체 소스 코드를 첨부하도록 하겠습니다.


<TIP> 코드 비교 사이트에서 기존 코드와 아래 코드를 양쪽에 붙여 넣고 Find Difference를 클릭하면 변경된 부분을 쉽게 확인할 수 있습니다. 실무에서도 유용하게 사용되는 사이트입니다.

코드 비교 사이트 화면 구조

 


<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="layout/basic">
    <th:block layout:fragment="title">
        <title>상세 페이지</title>
    </th:block>

    <th:block layout:fragment="content">
        <div class="page_tits">
            <h3>게시판 관리</h3>
            <p class="path"><strong>현재 위치 :</strong> <span>게시판 관리</span> <span>리스트형</span> <span>상세정보</span></p>
        </div>

        <div class="content">
            <section>
                <table class="tb tb_row">
                    <colgroup>
                        <col style="width:10%;"/><col style="width:23%;"/><col style="width:10%;"/><col style="width:23%;"/>
                    </colgroup>
                    <tbody>
                        <tr>
                            <th scope="row">글 유형</th>
                            <td th:text="${post.noticeYn == false ? '일반' : '공지'}"></td>

                            <th scope="row">등록일</th>
                            <td th:text="${#temporals.format( post.createdDate, 'yyyy-MM-dd HH:mm' )}"></td>
                        </tr>
                        <tr>
                            <th scope="row">제목</th>
                            <td>[[ ${post.title} ]]</td>

                            <th scope="row">조회</th>
                            <td colspan="3">[[ ${post.viewCnt} ]]</td>
                        </tr>
                        <tr>
                            <th scope="row">이름</th>
                            <td colspan="3">[[ ${post.writer} ]]</td>
                        </tr>
                        <tr>
                            <th scope="row">내용</th>
                            <td colspan="3">[[ ${post.content} ]]</td>
                        </tr>
                    </tbody>
                </table>
                <p class="btn_set">
                    <a th:href="@{/post/write.do( id=${post.id} )}" class="btns btn_bdr4 btn_mid">수정</a>
                    <button type="button" onclick="deletePost();" class="btns btn_bdr1 btn_mid">삭제</button>
                    <a th:href="@{/post/list.do}" class="btns btn_bdr3 btn_mid">뒤로</a>
                </p>
            </section>
        </div> <!--/* .content */-->
    </th:block>

    <th:block layout:fragment="script">
        <script th:inline="javascript">
        /*<![CDATA[*/

            // 게시글 삭제
            function deletePost() {
                const id = [[ ${post.id} ]];

                if ( !confirm(id + '번 게시글을 삭제할까요?') ) {
                    return false;
                }

                const formHtml = `
                    <form id="deleteForm" action="/post/delete.do" method="post">
                        <input type="hidden" id="id" name="id" value="${id}" />
                    </form>
                `;
                const doc = new DOMParser().parseFromString(formHtml, 'text/html');
                const form = doc.body.firstChild;
                document.body.append(form);
                document.getElementById('deleteForm').submit();
            }

        /*]]>*/

        </script>
    </th:block>
</html>

 

deletePost( )

61~63번 라인

먼저, 61~63번 라인의 코드를 통해 게시글을 삭제할지 최종적으로 선택합니다. confirm( ) 함수의 확인과 취소 버튼은 각각 true와 false를 리턴합니다.

 

 

65~73번 라인

65~73번 라인은 PageController의 deletePost( )를 호출하기 위한 로직입니다. formHtml에 deletePost( )를 호출하기 위한 폼 정보(id, action, method)를 선언하고, 폼에 hidden 타입으로 게시글 번호(id)를 담습니다. 이렇게 되면 폼 전송(submit) 시점에 hidden 타입의 id가 컨트롤러의 deletePost( )로 전송되며, 전달받은 id를 이용해서 게시글을 삭제 처리합니다.

 

69번 라인 이후의 로직이 조금 낯설게 느껴지실 수 있을 텐데요. new DOMParser( ).parseFromString( ) 함수는 문자열(String)로 그려진 HTML을 HTML 엘리먼트(Node)로 파싱 해주는 기능을 합니다. 71번 라인의 form은 doc에서 파싱 된 폼 엘리먼트(Node)를 꺼내오는 역할을 합니다. 마지막으로 HTML body에 폼 엘리먼트(Node)를 추가해서 submit을 발생시키면 PostController의 deletePost( ) 메서드가 실행됩니다.

 

사실, 콘텐츠(content) 영역에 폼(deleteForm)을 미리 그려두고, 삭제 로직이 실행되는 시점에 단순히 submit만 발생시켜도 되지만, HTML에 동적으로 폼을 추가해서 처리하는 방법도 있다는 걸 여러분께 공유하고 싶었습니다.

 

3. 게시글 삭제해 보기

현재 게시글 데이터는 총 두 개가 있습니다. 2번 게시글 상세 페이지로 이동해서 "삭제" 버튼을 클릭해 보겠습니다.

(이미지 사이즈 문제로 헤더, 푸터, 좌측 메뉴는 캡처에서 제외시켰습니다.)

게시글 리스트 페이지

 

 

삭제 확인(confirm) 창이 열렸습니다. 취소를 클릭하면 이벤트가 중단되고, 확인을 클릭하면 삭제 로직이 실행됩니다. 여기서는 확인을 클릭하겠습니다.

게시글 상세 페이지 - 삭제 버튼을 클릭한 화면

 

 

삭제된 후에는 리스트 페이지로 리다이렉트 되며, "개똥이"가 작성한 2번 글은 리스트에 노출되지 않고 있습니다.

게시글 리스트 페이지 - 2번 게시글이 삭제된 화면

 

 

우리는 물리 삭제가 아닌 논리 삭제 방식을 이용하고 있으니, 혹시라도 게시글 삭제를 취소하고 싶다면, 해당 게시글의 delete_yn을 '0'으로 업데이트해 주면 됩니다.

tb_post 테이블 SELECT 결과

 

마치며

여기까지 게시글 CRUD 작업이 모두 마무리되었습니다. 여기까지 오시느라 정말 고생 많으셨습니다!

 

다음 글부터는 개발의 퀄리티를 향상할 수 있는 몇 가지 기능들을 프로젝트에 적용하고, 적용이 완료되면 게시판에서 빠져서는 안 될, 페이징(검색) 처리, REST API 방식의 댓글 처리, 파일 업로드 처리 등 부가적인 기능들의 개발(구현) 방법을 포스팅할 예정입니다.

 

오늘도 방문해 주신 여러분께 진심으로 감사드립니다. 좋은 하루 보내세요 :)


앞으로 프로젝트에 적용할 기능들은 다음과 같습니다.

  • 컨트롤러에서 알러트(Alert) 메시지를 보여주고, 동시에 리다이렉트(Redirect) 처리하기
  • 로그백(Logback)을 적용해서 XML Mapper 쿼리 로그 출력하기
  • 인터셉터(Interceptor) 적용하기
  • AOP와 트랜잭션(Transaction) 적용하기

 

Board.zip
0.65MB

반응형

댓글