본문으로 바로가기

 

게시판의 글을 수정하고 삭제하는 것처럼 게시글에 포함된 첨부파일도 수정, 삭제를 할 수 있게 만들어 주자.

 

1. 수정 페이지에 파일 정보 가져오기

수정 페이지에 게시글의 정보와 함께 등록된 파일의 정보들도 같이 가져오자. 기존의 수정 페이지의 로직에서 파일의 정보를 가져오는 로직을 추가하여 수정 페이지에 정보를 던져주었다.

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
// BoardController (GET)
@RequestMapping(value="/board/boardUpdate")
public ModelAndView boardUpdate(CommandMap commandMap, Criteria cri) throws Exception {
    
    ModelAndView mv = new ModelAndView("/board/boardUpdate");
    Map<String, Object> detail = boardService.selectBoardDetail(commandMap.getMap());
    List<Map<String, Object>> file = boardService.selectBoardFileDetail(commandMap.getMap());
    mv.addObject("detail",detail);
    mv.addObject("file", file);
    
    PageMaker pageMaker = new PageMaker();
    pageMaker.setCri(cri);
    mv.addObject("page",cri.getPage());
    mv.addObject("pageMaker", pageMaker);
        
    return mv;
}
 
// BoardService
List<Map<String, Object>> selectBoardFileDetail(Map<String, Object> map);
 
// BoardServiceImpl
@Override
public List<Map<String, Object>> selectBoardFileDetail(Map<String, Object> map) {
    // TODO Auto-generated method stub
    return boardDAO.detailFile(map);
}
 
// BoardDAO
@SuppressWarnings("unchecked")
public List<Map<String, Object>> detailFile(Map<String, Object> map) {
    return selectList("board.detailFile", map);
}
 
// SQL
<select id="selectFileInfo" parameterType="hashmap" resultType="hashmap">
    <![CDATA[
     SELECT
         IDX,
         BOARD_IDX,
         ORG_FILE_NAME,
         SAVE_FILE_NAME
     FROM
         tb_file
     WHERE
         IDX = #{ IDX}
    ]]>
</select>
cs

7, 9행처럼 controller 에서 파일 정보를 가져오는 로직을 추가하여 servcie 영역을 추가하였고, dao, sql 영역은 상세 페이지에서 사용하던 기존 로직을 그대로 사용했다. 나처럼 controller 영역에서 file 정보를 가져오는 방법을 사용했지만, service 영역에서 file 정보를 가져오는 방법을 사용해도 된다. 

 

2. 수정 페이지에 파일 정보 보여주기

수정 페이지에서 등록된 파일을 보여주고 삭제 버튼을 추가할 것이다. 그리고 기존 파일 삭제 뿐만 아니라 새로운 파일을 추가할 수 있어야 되기 때문에 등록 페이지에 있는 파일 추가하는 로직도 그대로 들고 왔다.

- boardUpdate.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
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
 
<form action='<c:url value='/board/boardUpdate${pageMaker.makeQueryPage(page) }'/>' method="post" enctype="multipart/form-data">
    ...
    <div class="form-group file-group" id="file-list">
        <div class="file-add">
            <a href="#this" onclick="addFile()"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> 파일추가</a>
        </div>
        <c:forEach items="${file }" var="file">
            <div class="file-input">
                <span class="glyphicon glyphicon-camera" aria-hidden="true"></span>
                ${file.ORG_FILE_NAME } <span>${file.FILE_SIZE }kb</span>
                <a href='#this' name='file-delete'>삭제</a>
            </div>
        </c:forEach>
     </div>
    ...
</form>
 
<script type="text/javascript">
    $(document).ready(function() {
        $("a[name='file-delete']").on("click"function(e) {
            e.preventDefault();
            deleteFile($(this));
        });
    })
 
    function addFile() {
        var str = "<div class='file-input'><input type='file' name='file'><a href='#this' name='file-delete'>삭제</a></div>";
        $("#file-list").append(str);
        $("a[name='file-delete']").on("click"function(e) {
            e.preventDefault();
            deleteFile($(this));
        });
    }
 
    function deleteFile(obj) {
        obj.parent().remove();
    }
</script>
cs

수정 페이지에서도 새로운 파일을 보내야하기 때문에 3행처럼 반드시 form 태그에 enctype="multipart/form-data" 넣어야 한다. 그리고 스크립트 영역은 파일 업로드에서 사용했던 로직을 그대로 가지고 왔다.

 

이렇게 작업하고 실행을 하면 아래와 같이 수정 페이지에서 등록된 파일 정보들도 함께 보이게 된다.

 

3. 기존 파일의 삭제 여부 판단하기

기존에 등록된 파일의 정보가 넘어오지 않는다면 삭제했다고 판단하려고 한다. 그러기 위해서 수정 페이지에서 기존 파일의 고유 번호(IDX)를 가지고 있어야 한다. hidden 타입을 이용해서 파일의 고유번호를 가지고 있자. 만약 삭제버튼을 누르면 해당 hidden 타입을 같이 지워주면 지운 파일의 고유번호는 넘어가지 않을 것이다.

- boardUpdate.jsp

1
2
3
4
5
6
7
8
<c:forEach items="${file }" var="file">
    <div class="file-input">
        <span class="glyphicon glyphicon-camera" aria-hidden="true"></span>
        ${file.ORG_FILE_NAME } <span>${file.FILE_SIZE }kb</span>
        <input type="hidden" name="FILE_${file.IDX }" value="true">
        <a href='#this' name='file-delete'>삭제</a>
    </div>
</c:forEach>
cs

5행의 input hidden 타입을 추가해서 파일의 고유번호를 form 태그에 같이 넘어가도록 했다. name을 FILE_(파일IDX) 형태로 만들었고 넘어온 키 값 중에 파일의 고유번호가 없으면 삭제됐다고 판단할 것이다. 키 값으로만 판단하기 때문에 value는 true로 넘겨주었다. 새로 추가한 파일은 mulitpartFile에 담겨져 있을 것이다.

 

4. 게시글 및 파일 업데이트 작업

service 영역에서 기존 파일 삭제 여부를 판단하여 파일을 삭제하고, 새로운 파일들을 받아 게시글 업로드와 같은 방식으로 업데이트를 진행할 것이다. 수정페이지에서 정의한 키 값으로 등록된 파일의 정보와 비교하여 삭제여부를 판단해보자.

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
// BoardController (POST)
@RequestMapping(value="/board/boardUpdate", method=RequestMethod.POST)
public ModelAndView boardUpdatePOST(CommandMap commandMap, Criteria cri, RedirectAttributes redAttr, MultipartFile[] file) throws Exception {
    
    ModelAndView mv = new ModelAndView("redirect:/board/boardDetail");
    mv.addObject("idx", commandMap.get("idx"));
    
    boardService.updateBoard(commandMap.getMap(), file);
 
    redAttr.addAttribute("page", cri.getPage());
    redAttr.addAttribute("perPagNum", cri.getPerPageNum());
    
    return mv;
}
 
// BoardService
void updateBoard(Map<String, Object> map, MultipartFile[] file) throws Exception;
 
// BoardServcieImpl
@Override
public void updateBoard(Map<String, Object> map, MultipartFile[] file) throws Exception {
   // TODO Auto-generated method stub
   boardDAO.updateBoard(map);
   List<Map<String, Object>> fileDetail = boardDAO.detailFile(map);
   for(int i=0; i<fileDetail.size(); i++) {
       String idx = "FILE_" + fileDetail.get(i).get("IDX");
       if(!map.containsKey(idx)) {
           Map<String, Object> list = fileDetail.get(i);
           boardDAO.updateDeleteFile(list);
       }
   }
   List<Map<String, Object>> fileList = fileUtils.parseFileInfo(map, file);
   for(int i=0; i<fileList.size(); i++) {
       boardDAO.insertFile(fileList.get(i));
   }
}
cs

- 3, 17,  21행: 새로 추가된 파일 정보를 가져오기 위해서 MultipartFile[] file 추가한다.

- 8행: service 영역에 file 정보들도 함께 넘겨서 업데이트한다.

- 24행: 기존에 등록된 파일 정보들을 가져온다.

- 25-31행: "FILE_(파일IDX)" 형식으로 키 값으로 넘어오기 때문에 기존 파일의 정보도 FILE_(파일IDX) 형식으로 바꿔서(26행) 비교한다. 만약 클라이언트에서 넘어온 값 중에 기존 파일의 정보가 없다면(27행) 파일을 삭제한다(29행).

- 32-35행: 새로 추가한 파일을 등록한다.

 

파일 삭제는 기존 파일이 삭제됐다고 판단(29행)됐을 때 DB에서 파일을 직접 삭제하지 않고 DEK_CHK 라는 컬럼을 Y로 업데이트하는 방식으로 작업했다. 그럴려면 상세 페이지에서 파일을 가져올 때 DEL_CHK 가 N인 파일들만 가져오는 작업이 필수로 되어 있어야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// BoardDAO
public void updateDeleteFile(Map<String, Object> list) {
   // TODO Auto-generated method stub
   update("board.deleteFile", list);
}
 
// SQL
<update id="deleteFile">
   <![CDATA[
       UPDATE
           tb_file
       SET
           DEL_CHK = 'Y'
       WHERE
           IDX = #{ IDX}
   ]]>
</update>
cs

 

5. 테스트

수정 페이지에서 컨텐츠 및 파일을 수정하는 테스트를 진행했다. 

&gt; 수정 전 상세 페이지
&gt; 컨텐츠를 수정하고 기존 파일 하나를 삭제하고 새로운 파일을 등록
&gt; 고유번호 2번 파일만 넘어온 것 확인
&gt; 삭제된 기존 파일 업데이트 하고 새로 추가된 파일 등록
&gt; 수정 후 상세페이지

 

기존 파일의 삭제 여부를 판단하는 작업은 내가 작업한 이 방법 말고도 다른 여러 방법들이 있다. 본인들이 이해하기 쉽고 편한걸로 사용하면 되지 않을까 싶다. 이렇게 게시판 만들기의 첨부 파일 관련 작업은 끝났다. 그렇게 오래 걸릴 일은 아닌데 현생이 바빠서 이제 마무리 글을 올렸다. 그래서 너무 오랜만에 보는 거라 머리가 멍하기도 했지만 오랜만에 스프링 작업을 해보니 재밌었다.