Servlet JSP MVC Spring

[MVC] 게시판 구현하기: 삭제기능, 답글기능

vhxpffltm 2020. 3. 31. 22:01
반응형

삭제하기

 

게시판의 글을 삭제할 때는 테이블의 글과 그 글의 자식 글 및 이미지도 함께 삭제해야 한다.

 

여기서 우리는 '삭제하기' 버튼을 클릭하면 글 번호에 따라 이와 관련된 모든 자식글들을 삭제한다. 물론 글에 함께있는 이미지 파일도 삭제할 것이다.

 

이를 위해,  Controller 클래스에서 삭제하기 전에 삭제할 글 번호와 자식 글 번호를 목록으로 가져온다. 그다음에 글을 삭제한 후 글 번호로 이루어진 이미지 폴더까지 모두 삭제하는 과정을 거친다.

 

새로운 패키지에 이전에 했던 내용들인 4개의 클래스와 3개의 JSP파일을 복사, 붙여넣기한다. BoardController 클래스에 추가된 부분먼저 살펴보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ....  중략
else if(action.equals("/removeArticle.do")) {
                int articleNO = Integer.parseInt(request.getParameter("articleNO"));
                List<Integer> articleNOList = boardService.removeArticle(articleNO);
                //articleNO 값에 대한 글을 삭제후 삭제된 부모 글과 자식글의 articleNO 목록을 가져옴
                for(int i : articleNOList) {
                    File imgDir = new File(ARTICLE_IMAGE_REPO + "\\" + i);
                    if(imgDir.exists()) FileUtils.deleteDirectory(imgDir);
                }// 삭제된 글들의 이미지 저장 폴더를 삭제
                PrintWriter pw = response.getWriter();
                pw.print("<script>" + "  alert('글을 삭제했습니다.');" + location.href='" + request.getContextPath()
                + "/board/listArticles.do';" + "</script>");
        return;
            }
            RequestDispatcher dispatch = request.getRequestDispatcher(nextPage);
            dispatch.forward(request, response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
/// Map 메소드....
 
 
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

 

컨트롤러 클래스에 새로운 else if문으로 삭제 요청이 왔을때 동작하는 코드이다. List<>객체로 글을 삭제한 후 삭제된 부모 글과 자식글의 articleNO를 가지고 온다. 이유는 그래야 마지막에 이미지 파일을 지울 수 있기 때문이다.

 

그다음 BoardService 클래스에 삭제하기 위한 메서드를 아래와 같이 추가한다.

 

1
2
3
4
5
6
7
8
9
10
// ....  중략
public List<Integer> removeArticle(int articleNO){
        List<Integer> articleNOList = boardDAO.selectRemovedArticles(articleNO);
        //글을 삭제하기전 글 번호들을 ArrayList 객체에 저장
        boardDAO.deleteArticle(articleNO);
        return articleNOList; // 삭제한 글 번호 목록을 컨트롤러로 반환
    }
 
 
 
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

 

이제 DB의 쿼리문을 실행시켜주는 DAO클래스에 코드를 추가한다. 위에서 말했듯이 먼저 BoardService 클래스에 있듯이 삭제하기전 글 번호를 ArrayList<> 객체에 저장한 후 삭제를 진행한다. 

 

그리고 오라클의 계층형 SQL을 이용해 삭제글과 관련된 자식글을 삭제한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// ....  중략
public void deleteArticle(int articleNO) {
        //전달된 articleNO에 대한 글을 삭제!!
        try {
            con = dataFactory.getConnection();
            String query = "DELETE FROM t_board";
            query += " WHERE articleNO in (";
            query += "  SELECT articleNO FROM  t_board ";
            query += " START WITH articleNO = ?";
            query += " CONNECT BY PRIOR  articleNO = parentNO )";
            //오라클의 계층형 SQL을 이용해 삭제글과 관련된 자식글까지 삭제
            System.out.println(query);
            pstmt = con.prepareStatement(query);
            pstmt.setInt(1, articleNO);
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        }catch(Exception e) {
            e.printStackTrace();
        }
    }
    public List<Integer> selectRemovedArticles(int articleNO){
        //삭제할 글에 대한 글 번호를 가져옴
        List<Integer> articleNOList = new ArrayList<Integer>();
        try {
            con = dataFactory.getConnection();
            String query = "SELECT articleNP FROM t_board";
            query += " START WITH articleNO = ?";
            query += " CONNECT BY PRIOR  articleNO = parentNO";
            //삭제된 글들의 articleNO 조회
            System.out.println(query);
            pstmt = con.prepareStatement(query);
            pstmt.setInt(1, articleNO);
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                articleNO = rs.getInt("articleNO");
                articleNOList.add(articleNO);
            }
            pstmt.close();
            con.close();
        }catch(Exception e) {
            e.printStackTrace();
        }
        return articleNOList;
    }
 
 
 
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

 

계층형 SQL문을 사용해 부모글에 대한 자식글 삭제는 위의 코드에도 있지만 SQL문에 약할 수 있으니 아래에 참조해놓는다.

 

1
2
3
4
5
6
7
8
9
DELETE FROM t_board
WHERE articleNO in{
    SELECT articleNO FROM t_board
    START WITH articleNO= '글번호'
    CONNECT BY PRIOR articleNO = parentNO
};
 
 
 
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

 

이제 마지막으로 viewArticle.jsp 파일을 아래와 같이 수정해준다. JSP는 다루지 않기에 전체 코드를 첨부한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"
    isELIgnored="false" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>    
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
  request.setCharacterEncoding("UTF-8");
%> 
<c:set var="contextPath"  value="${pageContext.request.contextPath}"  />
<head>
   <meta charset="UTF-8">
   <title>글보기</title>
   <style>
     #tr_btn_modify{
       display:none;
     }
   
   </style>
   <script  src="http://code.jquery.com/jquery-latest.min.js"></script> 
   <script type="text/javascript" >
     function backToList(obj){
        obj.action="${contextPath}/board/listArticles.do";
        obj.submit();
     }
 
     function fn_enable(obj){
         document.getElementById("i_title").disabled=false;
         document.getElementById("i_content").disabled=false;
         document.getElementById("i_imageFileName").disabled=false;
         document.getElementById("tr_btn_modify").style.display="block";
         document.getElementById("tr_btn").style.display="none";
     }
     
     function fn_modify_article(obj){
         obj.action="${contextPath}/board/modArticle.do";
         obj.submit();
     }
     
     function fn_remove_article(url,articleNO){
         var form = document.createElement("form");
         form.setAttribute("method""post");
         form.setAttribute("action", url);
         var articleNOInput = document.createElement("input");
         articleNOInput.setAttribute("type","hidden");
         articleNOInput.setAttribute("name","articleNO");
         articleNOInput.setAttribute("value", articleNO);
         
         form.appendChild(articleNOInput);
         document.body.appendChild(form);
         form.submit();
     
     }
     
     function readURL(input) {
         if (input.files && input.files[0]) {
             var reader = new FileReader();
             reader.onload = function (e) {
                 $('#preview').attr('src'e.target.result);
             }
             reader.readAsDataURL(input.files[0]);
         }
     }  
 </script>
</head>
<body>
  <form name="frmArticle" method="post"  action="${contextPath}"  enctype="multipart/form-data">
  <table  border="0" align="center" >
  <tr>
   <td width="150" align="center" bgcolor="#FF9933">
      글번호
   </td>
   <td >
    <input type="text"  value="${article.articleNO }"  disabled />
    <input type="hidden" name="articleNO" value="${article.articleNO}"  />
   </td>
  </tr>
  <tr>
    <td width="150" align="center" bgcolor="#FF9933">
      작성자 아이디
   </td>
   <td >
    <input type=text value="${article.id }" name="writer"  disabled />
   </td>
  </tr>
  <tr>
    <td width="150" align="center" bgcolor="#FF9933">
      제목 
   </td>
   <td>
    <input type="text" value="${article.title }"  name="title"  id="i_title" disabled />
   </td>   
  </tr>
  <tr>
    <td width="150" align="center" bgcolor="#FF9933">
      내용
   </td>
   <td>
    <textarea rows="20" cols="60"  name="content"  id="i_content"  disabled />${article.content }</textarea>
   </td>  
  </tr>
 
<c:if test="${not empty article.imageFileName && article.imageFileName!='null' }">  
<tr>
    <td width="150" align="center" bgcolor="#FF9933"  rowspan="2">
      이미지
   </td>
   <td>
     <input  type= "hidden"   name="originalFileName" value="${article.imageFileName }" />
    <img src="${contextPath}/download.do?articleNO=${article.articleNO}&imageFileName=${article.imageFileName}" id="preview"  /><br>
       
   </td>   
  </tr>  
  <tr>
    <td>
       <input  type="file"  name="imageFileName " id="i_imageFileName"   disabled   onchange="readURL(this);"   />
    </td>
  </tr>
 </c:if>
  <tr>
        <td width="150" align="center" bgcolor="#FF9933">
          등록일자
       </td>
       <td>
        <input type=text value="<fmt:formatDate value="${article.writeDate}" />" disabled />
       </td>   
  </tr>
  <tr   id="tr_btn_modify"  >
       <td colspan="2"   align="center" >
           <input type=button value="수정반영하기"   onClick="fn_modify_article(frmArticle)"  >
           <input type=button value="취소"  onClick="backToList(frmArticle)">
       </td>   
  </tr>
    
  <tr  id="tr_btn"    >
   <td colspan=2 align=center>
        <input type=button value="수정하기" onClick="fn_enable(this.form)">
        <input type=button value="삭제하기" onClick="fn_remove_article('${contextPath}/board/removeArticle.do', ${article.articleNO})">
        <input type=button value="리스트로 돌아가기"  onClick="backToList(this.form)">
         <input type=button value="답글쓰기"  onClick="fn_reply_form('${contextPath}/board/replyForm.do', ${article.articleNO})">
   </td>
  </tr>
 </table>
 </form>
</body>
</html>
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

 

이제 프로그램을 실행하고 listArticle.do 로 접속하여 아래와 같이 현재 게시판 상태를 보자.

 

 

여기서 오타가 있는 '5번 글'을 들어가 '삭제하기' 버튼을 누르면 삭제가 완료됨을 볼 수 있다.

 

삭제가 되면서 글번호들이 정렬되었다. 오타가 있던 아까의 글이 없어진것을 확인할 수 있다.

이미지가 게시된 글도 삭제가 진행되는데 현재 이미지가 저장되어 있는 디렉토리가 삭제되지 않는 현상이 있을 수 있다. 전체 디렉토리와 게시글들을 삭제하여 초기화하고 실험을 진행해보길 바란다.

 

 

답글 쓰기

 

답글, 댓글기능이다. 게시글에 대한 글을 쓸수 있는 기능으로 이 기능은 아래와 같이 설계한다.

 

글 상세창에서 '답글쓰기' 를 클릭하면 부모 글 번호를 컨트롤러로 요청한다.

답글 쓰기창에서 답글을 작성한 후, 새로운 요청으로 컨트롤러에 요청한다.

컨트롤러는 전송된 답글 정보를 게시판 테이블에 부모 글 번호와 함께 추가한다.

바로 해보자.

 

위에서 진행한 4개의 클래스를 새로운 패키지에 복사하고 JSP도 복사한다. 그리고 답글 요청 JSP인 reply.jsp도 추가한다.

 

컨트롤러 클래스부터 살펴보자

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
////......
else if(action.equals("/replyForm.do")) {
    int parentNO = Integer.parseInt(request.getParameter("parentNO"));
    System.out.println(parentNO);
    session = request.getSession();
    session.setAttribute("parentNO", parentNO);
    nextPage = "/board06/replyForm.jsp";
    //답글창 요청시 미리 부모 글 번호를 parentNO속성으로 세션에 저장
    }
else if (action.equals("/addReply.do")) {
    session = request.getSession();
    int parentNO = (Integer) session.getAttribute("parentNO");
    System.out.println(parentNO);
    session.removeAttribute("parentNO");
    //답글 전솔 시 세션에 저장된 parentNO를 가져옴
    Map<StringString> articleMap = upload(request, response);
    String title = articleMap.get("title");
    String content = articleMap.get("content");
    String imageFileName = articleMap.get("imageFileName");
    articleVO.setParentNO(parentNO); //답글의 부모 글 번호를 설정
    articleVO.setId("SON"); // 답글 작성자를 설정
    articleVO.setTitle(title);
    articleVO.setContent(content);
    articleVO.setImageFileName(imageFileName);
    int articleNO = boardService.addReply(articleVO);
    if (imageFileName != null && imageFileName.length() != 0) {
        File srcFile = new File(ARTICLE_IMAGE_REPO + "\\" + "tep" + "\\" + imageFileName);
        File destDir = new File(ARTICLE_IMAGE_REPO + "\\" + articleNO);
        destDir.mkdirs();
        FileUtils.moveFileToDirectory(srcFile, destDir, true);
        }// 답글에 첨부한 이미지를 tep폴더에서 답글 번호 폴더로 이동
    PrintWriter pw = response.getWriter();
    pw.print("<script>" + "  alert('답글을 추가했습니다.');" + location.href='" + request.getContextPath()
                    + "/board/viewArticle.do?articleNO="+articleNO+"';" + "</script>");
    return;
}
///....
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

 

답글쓰기는 '새글쓰기'와 비슷하다. 차이점은 미리 글 번호를 parentNO 속성으로 세션에 저장하고 답글을 작성한 후 등록을 요청하면 세션에서 parentNO를 가져와 테이블에 추가한다. 

 

답글은 Controller 클래스만 제대로 작성하면 끝이다. 그리고 Service 클래스에 아래의 내용을 추가한다.

 

1
2
3
4
public int addReply(ArticleVO article) {
        return boardDAO.insertNewArticle(article);
        //새 글 추가시 사용한 insert... 메서드를 그대로 이용해 답들로 추가한다.
    }
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

 

위의 메서드를 보면 새글을 작성하는 메서드를 그대로 사용한다. 즉, 더이서 우리는 클래스파일을 수정할 필요가 없다.

 

이제 JSP파일을 보자. viewArticle.jsp에 '답글쓰기' 버튼에 함수를 호출하도록 한다. 함수부분과 form태그를 아래와 같이 수정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- ...중략 ... -->
 function fn_reply_form(url, parentNO){
         var form = document.createElement("form");
         form.setAttribute("method", "post");
         form.setAttribute("action", url);
         var parentNOInput = document.createElement("input");
         parentNOInput.setAttribute("type","hidden");
         parentNOInput.setAttribute("name","parentNO");
         parentNOInput.setAttribute("value", parentNO);
         
         form.appendChild(parentNOInput);
         form.submit();
     }
<!-- ...중략 ... -->
<input type=button value="답글쓰기"  onClick="fn_reply_form('${contextPath}/board/replyForm.do', ${article.articleNO})">
<!-- ... 중략 ... -->
 
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

 

그리고 이제 답글을 다는 새로운 jsp파일인 replyForm.jsp를 만들어보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"
    isELIgnored="false" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>    
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="contextPath"  value="${pageContext.request.contextPath}"  />
<%
  request.setCharacterEncoding("UTF-8");
%> 
<head>
<meta charset="UTF-8">
<script type="text/javascript">
  function backToList(obj){
     obj.action="${contextPath}/board/listArticles.do";
     obj.submit();
  }
 
  function readURL(input) {
      if (input.files && input.files[0]) {
          var reader = new FileReader();
          reader.onload = function (e) {
              $('#preview').attr('src'e.target.result);
          }
          reader.readAsDataURL(input.files[0]);
      }
  }  
</script> 
<title>답글쓰기 페이지</title>
</head>
<body>
 <h1 style="text-align:center">답글쓰기</h1>
  <form name="frmReply" method="post"  action="${contextPath}/board/addReply.do"   enctype="multipart/form-data">
    <table align="center">
    <tr>
            <td align="right"> 글쓴이:&nbsp; </td>
            <td><input type="text" size="5" value="SON" disabled /> </td>
        </tr>
        <tr>
            <td align="right">글제목:&nbsp;  </td>
            <td><input type="text" size="67"  maxlength="100" name="title" /></td>
        </tr>
        <tr>
            <td align="right" valign="top"><br>글내용:&nbsp; </td>
            <td><textarea name="content" rows="10" cols="65" maxlength="4000"> </textarea> </td>
        </tr>
        <tr>
            <td align="right">이미지파일 첨부:  </td>
            <td> <input type="file" name="imageFileName"  onchange="readURL(this);" /></td>
            <td><img  id="preview" src="#"   width=200 height=200/></td>
        </tr>
        <tr>
            <td align="right"> </td>
            <td>
                <input type=submit value="답글반영하기" />
                <input type=button value="취소"onClick="backToList(this.form)" />
                
            </td>
        </tr>
    </table>
  </form>
</body>
</html>
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

 

여기까지 진행화면 수정기능이 완료되었다. 테스트 해보자.

 

테스트를 진행할때 새로운 글을 만들고 그 글에다 답글을 달아야한다. 이전에 달았던 글에 답글을 달려면 parentNO에 에러가 발생한다. 그리고 글을 추가할때 이미지 파일도 함께 추가하도록 하자.

 

반응형