본 내용은 혼자 공부하기 위하여 다른 포스트를 보면서 저에게 필요한 부분과 궁금했던 부분을 추가하여 게시하는 곳입니다.
궁금한 것에 대한 것은 모르는 것이 많겠지만 함께 알아보도록 노력하겠습니다.
참조 게시 포스트 : http://addio3305.tistory.com/
------------------------------------------------------------------------------------------------------------------------------------------
1. 파일 다중 업로드
1.1 JSP
파일 다중 업로드를 위해 jsp 파일을 수정하겠다.
boardWrite.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>글쓰기</title> <%@ include file="/WEB-INF/include/include-header.jspf" %> </head> <body> <form id="frm" enctype="multipart/form-data" > <table class="board_view"> <colgroup> <col width="15%" > <col width="*" > </colgroup> <caption>게시글 작성</caption> <tbody> <tr> <th scope="row">제목</th> <td><input type="text" name="TITLE" class="wdp_90" /></td> </tr> <tr> <th scope="row">내용</th> <td><textarea cols="100" rows="20" id="CONTENTS" name="CONTENTS" title="내용"></textarea></td> </tr> </tbody> </table> <div id="fileDiv"> <p> <input type="file" name="file_0"/> <a href="#this" name="delete" class="btn">삭제하기</a> </p> </div> <br/><br/> <a href="#this" id="add" class="btn">파일 추가하기</a> <a href="#this" id="list" class="btn">목록으로</a> <a href="#this" id="write" class="btn">글쓰기</a> </form> <%@ include file="/WEB-INF/include/include-body.jspf" %> <script type="text/javascript"> var g_count =1; $(document).ready(function(){ $("#list").on("click",function(e){ e.preventDefault(); fn_openBoardList(); }) $("#write").on("click",function(e){ e.preventDefault(); fn_writeBoard(); }) $("a[name='delete']").on("click",function(e){ e.preventDefault(); fn_fileDelete($(this)); }) $("#add").on("click",function(e){ e.preventDefault(); fn_fileAdd(); }) }); function fn_openBoardList(){ var comSubmit = new ComSubmit(); comSubmit.setUrl("<c:url value='/sample/openBoardList.do'/>"); comSubmit.submit(); } function fn_writeBoard(){ var comSubmit = new ComSubmit("frm"); comSubmit.setUrl("<c:url value='/sample/writeBoard.do'/>"); comSubmit.submit(); } function fn_fileDelete(obj){ obj.parent().remove(); } function fn_fileAdd(){ var str = "<p><input type='file' name='file_"+(g_count++)+"'/><a href='#this' name='delete' class='btn'>삭제하기</a></p> "; $("#fileDiv").append(str); $("a[name='delete']").on("click",function(e){ e.preventDefault(); fn_fileDelete($(this)); }) } </script> </body> </html>
- 31 줄
여기서 name이 file_0로 한 이유가 설명이 된다. 다른 파일들과 name에서 뒤의 숫자로 각각을 판단하여 추가하거나 삭제할 수 있도록 하기 위하여 name을 설정해주었다.
- 32,36 줄
삭제하기와 파일 추가하기 버튼을 생성해주었다. 이 버튼들로 더 등록할 파일을 추가하거나 삭제할 수 있다.
-43 줄
g_count 라는 것이 파일들을 더 추가할 때 서로 구분하기 위하여 name 뒤에 숫자를 붙일 때 쓰는 변수이다.
- 53~60 줄
삭제하기와 파일 추가하기 버튼에 대한 이벤트 처리를 주었다.
- 75~77 줄
삭제하기 버튼의 처리 중 해당 버튼의 부모 클래스를 지움으로써 파일 부분을 지우는데 여기서 부모 클래스는 fileDiv 안에 포함되어 있는 <p> 태그이다. 따라서 <p>태그를 지우면서 <input type="file"> 도 함께 지워진다.
- 78~85 줄
파일 추가하기 버튼의 처리로 <p>태그로 묶은 부분을 전체적으로 다 변수에 넣고 <div>태그에 추가하여 준다. 또한 해당 삭제하기 버튼에도 따로 이벤트 처리를 줌으로써 각각 고유의 삭제하기 버튼의 처리 기능이 완성 된다.
여기까지하면 전에 파일 업로드를 할 때 리스트와 반복물을 통해 파일들에 대한 처리를 서비스단이나 FileUtils에서 구성해주었기 때문에 문제없이 될 것이다. 상세보기에서도 첨부파일 부분은 반복문을 써놓았기 때문에 따로 수정할 필요는 없다.
1.2 테스트
다음과 같이 작성하고 파일을 2개 추가하였다.
- 다음 화면은 그 결과로 상세보기 화면에서도 2개의 파일을 볼 수 있다.
- 다음은 로그 화면으로 2개의 insertFile의 DAO 로그와 SQL 로그를 볼 수 있다.
2. 게시판 수정하기(파일 업로드)
앞서 게시판 수정하기에서는 글 제목과 글 내용만 수정하였다. 이제는 파일 업로드라는 새로운 것이 추가 되었기에 새로 코드를 수정하여야 한다. 하지만 수정하기에 앞서 어떤 식으로 만들 지에 대해 고민해 보아야 한다.
2.1 게시판 수정하기 처리
- 게시글의 내용만 수정하고 파일은 그대로한다.
- 기존의 파일을 변경한다.
- 기존의 파일을 삭제한다.
- 기존의 파일을 두고 새로운 파일을 추가한다.
- 기존의 파일을 삭제하고 파일을 추가한다.
- 기존의 파일을 수정하고 파일을 추가한다.
... 등등 여러가지 처리에 대한 방법이 필요할 것이다.
모두 같은 방법을 쓰는 것은 아니고 여기에서는 해당 게시물에 포함되는 모든 파일들을 삭제처리(DEL_GB="Y")를 하고 기존의 파일이 변경되면 새롭게 추가하고 파일이 추가되면 또 새롭게 추가하고 삭제했을 경우 그대로 두고 그대로 두었을 땐 삭제처리만 번복(DEL_GB="N")을 해주는 방식을 사용할 것이다. 그렇게 하기 위하여 먼저 Controller 부분을 작성하자.
2.2 Controller
SampleController.java
@RequestMapping(value="/sample/openBoardModify.do") public ModelAndView openBoardModify(CommandMap commandMap) throws Exception{ ModelAndView mv = new ModelAndView("/sample/boardModify"); Map<String,Object> map = sampleService.selectBoard(commandMap.getMap()); mv.addObject("map",map.get("map")); mv.addObject("list",map.get("list")); return mv; } @RequestMapping(value="/sample/modifyBoard.do") public ModelAndView modifyBoard(CommandMap commandMap, HttpServletRequest req) throws Exception{ ModelAndView mv = new ModelAndView("redirect:/sample/openBoardDetail.do"); sampleService.modifyBoard(commandMap.getMap(),req); mv.addObject("IDX", commandMap.get("IDX")); return mv; }
- openBoardModify를 수정하자. Detail과 동일하게 파일을 넘겨줄 수 있게 list를 따로 할당해주었다. 크게 변경한 것은 없다. 또한 modifyBoard에도 파라미터로 HttpServletRequest 를 추가하였고 Service 단에도 같이 넘겨주었다.
2.3 Service
SampleServiceImpl.java
@Override public void modifyBoard(Map<String, Object> map, HttpServletRequest req) throws Exception { sampleDAO.modifyBoard(map); sampleDAO.deleteFile(map,req); List<Map<String, Object>> list = fileUtils.parseUpdateFileInfo(map, req); Map<String,Object> tempMap = null; for(int i = 0; i<list.size();i++) { tempMap = list.get(i); if(tempMap.get("IS_NEW").equals("Y")) { sampleDAO.insertFile(tempMap); }else { sampleDAO.updateFile(tempMap); } } }
- modifyBoard를 수정하자. HttpServletRequest 파라미터를 추가하였다.
- 5 줄
deleteFile 이라는 것은 아까 위에서 말했듯이 해당 게시물에 포함되는 해당 파일들을 삭제하는 부분이다. (DEL_GB="Y")
- 7 줄
우리가 만든 fileUtils 클래스의 메서드를 쓰는 것으로 parseInserFileInfo와 비슷한 기능을 하는 것으로 파일 정보를 list에 반환한다.
- 9~17 줄
반환 받은 리스트들, 즉 파일들에 IS_NEW라는 키가 있다. 해당 키는 새롭게 추가된 파일인지 기존의 파일인지를 구분하기 위해 만든 키로 Y는 새로운 파일로 insert 처리를 하고, N은 기존의 파일로 하여 update 처리를 할 수 있게 해주었다.
2.4 DAO
SampleDAO.java
public void deleteFile(Map<String, Object> map, HttpServletRequest req) { update("sample.deleteFile",map); } public void updateFile(Map<String, Object> map) { update("sample.updateFile",map); }
- deleteFile과 updateFile 에 대한 sql 처리를 담당하는 메서드들을 추가하였다. Service 단에서 사용했던 것들로 insertFile은 기존에 이미 있다.
2.5 SQL
sample_SQL.xml
<update id="sample.deleteFile" parameterType="hashmap"> <![CDATA[ UPDATE TB_FILE1 SET DEL_GB = 'Y' WHERE BOARD_IDX=#{IDX} ]]> </update> <update id="sample.updateFile" parameterType="hashmap"> <![CDATA[ UPDATE TB_FILE1 SET DEL_GB = 'N' WHERE IDX=#{FILE_IDX} ]]> </update>
- deleteFile에서 IDX는 게시글의 번호로 게시글에 해당 되는 모든 파일을 지우기 위하기 위함이고, updateFile에서 IDX는 파일 번호로 기존에 있던 파일 중 변경되지 않은 파일을 다시 복구하는 것이다. 물론 삭제는 DELETE 하는 것이 아니라 DEL_GB를 사용하여 기존에 보이지 않도록만 하는 것이다.
2.6 FileUtils
FileUtils.java
public List<Map<String, Object>> parseUpdateFileInfo(Map<String, Object> map, HttpServletRequest req) throws Exception { MultipartHttpServletRequest mulReq = (MultipartHttpServletRequest)req; MultipartFile mulFile=null; String original_File_Name = null; String stored_File_Name = null; String original_Extension = null; Iterator<String> iterator = mulReq.getFileNames(); String board_IDX = map.get("IDX").toString(); String IDX=null; List<Map<String,Object>> fileList = new ArrayList<Map<String,Object>>(); Map<String,Object> fileMap = null; while(iterator.hasNext()) { mulFile = mulReq.getFile(iterator.next()); if(mulFile.isEmpty()==false) { original_File_Name = mulFile.getOriginalFilename(); original_Extension = original_File_Name.substring(original_File_Name.lastIndexOf(".")); stored_File_Name = CommonUtils.getRandomString() + original_Extension; mulFile.transferTo(new File(filePath+stored_File_Name)); fileMap = new HashMap<String,Object>(); fileMap.put("BOARD_IDX", board_IDX); fileMap.put("ORIGINAL_FILE_NAME", original_File_Name); fileMap.put("STORED_FILE_NAME", stored_File_Name); fileMap.put("FILE_SIZE", mulFile.getSize()); fileMap.put("IS_NEW","Y"); fileList.add(fileMap); }else { String requestName = mulFile.getName(); IDX = "IDX_"+requestName.substring(requestName.indexOf("_")+1); if(map.containsKey(IDX) == true &&map.get(IDX)!=null) { fileMap = new HashMap<String,Object>(); fileMap.put("IS_NEW", "N"); fileMap.put("FILE_IDX",map.get(IDX)); fileList.add(fileMap); } } } return fileList; }
- 전에 작성했던 parseInsertFileInfo와 매우 흡사한 구조이다. 특별히 다른 것이라고는 파일 번호의 IDX가 필요하여 변수로 하여 받아온다는 것과 14~42줄 부분이다.
- 14~42줄
기존과 다르게 fileMap에 IS_NEW 키를 넣는 것을 알 수 있다. 위에서 말했듯이 새로운 파일이냐 아니냐를 구분하는 키이다. 기존의 파일인지 아닌지는 17줄의 if(mulFile.isEmpty()==false)를 통해서 알 수 있는데 수정하기 페이지에서 기존의 파일을 그대로 둘 시에는 Request에 포함되지 않는데 이는 <input type="file"> 태그로 넘겨져 오는데 이 부분이 없기 때문이다. 따라서 기존의 파일과 새 파일들은 17줄로 구분을 할 수 있다. 그러면 어떤 식으로 기존 파일 중에서도 삭제된 파일이나 수정된 파일 그리고 그대로 둔 파일을 구분 짓는 지에 대해서는 33~35줄을 보면 된다. mulFile로 부터 Name을 받아오는데 이는 <input type="file" > 태그의 name 속성에 해당하는 부분을 가져온다.
예시로 작성을 해보자면 <input type="file" name="file_0"> 이라면 file_0을 가져오는 것이다. requestName에는 file_0이 있는 상태에서 34줄이 IDX = "IDX_" + "0" 처럼 해석이 되는 것이다. IDX = IDX_0이 되어 map.containsKey(IDX)를 통해 수정하기 페이지 중에 name이 IDX_0이 있는 것과 값을 갖고 있는 지 확인 뒤 그 값을 fileMap에 추가하는 것을 볼 수 있다. 물론 name이 IDX_0인 태그의 값은 기존 파일의 IDX 값을 갖고 있다.
2.7 JSP
boardModify.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>글 수정하기</title> <%@ include file="/WEB-INF/include/include-header.jspf" %> </head> <body> <form id="frm" enctype="multipart/form-data"> <table class="board_view"> <caption>글 수정하기</caption> <colgroup> <col width="15%"> <col width="35%"> <col width="15%"> <col width="35%"> </colgroup> <tbody> <tr> <th scope="row">글 번호</th> <td> ${map.IDX } <input type="hidden" id="IDX" name="IDX" value="${map.IDX }"> </td> <th scope="row">조회수</th> <td>${map.HIT_CNT }</td> </tr> <tr> <th scope="row">작성자</th> <td>${map.CREA_ID }</td> <th scope="row">작성시간</th> <td>${map.CREA_DTM }</td> </tr> <tr> <th scope="row">제목</th> <td colspan="3"> <input type="text" id="TITLE" name="TITLE" class="wdp_90" value="${map.TITLE }"/> </td> </tr> <tr> <th scope="row">내용</th> <td colspan="3" class="view_text"> <textarea rows="20" cols="100" title="내용" id="CONTENTS" name="CONTENTS">${map.CONTENTS }</textarea> </td> </tr> <tr> <th>첨부파일</th> <td colspan="3"> <div id="fileDiv"> <c:forEach items="${list }" varStatus="row" var="var" > <p> <input name="IDX_${row.index }" id="IDX" type="hidden" value="${var.IDX }"/> <a href="#this" name="name_${row.index }" id="name_${row.index }">${var.ORIGINAL_FILE_NAME }</a> <input type="file" name="file_${row.index }"> <a href="#this" id="delete_${row.index }" name="delete_${row.index }" class="btn">삭제하기</a> </p> </c:forEach> </div> </td> </tr> </tbody> </table> </form> <a href="#this" id="list" class="btn">목록으로</a> <a href="#this" id="add" class="btn">파일 추가하기</a> <a href="#this" id="modify" class="btn">수정하기</a> <a href="#this" id="delete" class="btn">삭제하기</a> <%@ include file="/WEB-INF/include/include-body.jspf" %> <script type="text/javascript"> var gfv_count = '${fn:length(list)+1}'; $(document).ready(function(){ $("#modify").on("click",function(e){ e.preventDefault(); fn_modifyBoard(); }) $("#list").on("click",function(e){ e.preventDefault(); fn_openBoardList(); }) $("#delete").on("click",function(e){ e.preventDefault(); fn_deleteBoard(); }) $("#add").on("click",function(e){ e.preventDefault(); fn_fileAdd(); }) $("a[name^='delete']").on("click",function(e){ e.preventDefault(); fn_fileDelete($(this)); }) }) function fn_fileAdd(){ var str = "<p>" + "<input type='file' id='file_"+(gfv_count)+"' name='file_"+(gfv_count)+"'>"+ "<a href='#this' class='btn' id='delete_"+(gfv_count)+"' name='delete_"+(gfv_count)+"'>삭제</a>" + "</p>"; $("#fileDiv").append(str); $("#delete_"+(gfv_count++)).on("click", function(e){ //삭제 버튼 e.preventDefault(); fn_fileDelete($(this)); }); } function fn_fileDelete(obj){ obj.parent().remove(); } function fn_modifyBoard(){ var comSubmit = new ComSubmit("frm"); comSubmit.setUrl("<c:url value='/sample/modifyBoard.do'/>"); comSubmit.submit(); } function fn_openBoardList(){ var comSubmit = new ComSubmit(); comSubmit.setUrl("<c:url value='/sample/openBoardList.do'/>"); comSubmit.submit(); } function fn_deleteBoard(){ var comSubmit = new ComSubmit("frm"); comSubmit.setUrl("<c:url value='/sample/deleteBoard.do'/>"); comSubmit.submit(); } </script> </body> </html>
- 52~57 줄
해당 부분이 기존 파일이 있고 파일을 추가하거나 변경하고 삭제하고 할 수 있는 부분이다. 위에서 말했던 기존 파일에서 IDX 값을 가져오는 부분으로 55줄을 보면 <input type="file" name="file_${row.index}"> 라고 되있는 부분이 있다. 이 부분의 name값이 위에서 말했던 것처럼 돌아가는 것이다. row.index라는 것은 forEach 구문에서 속성 varStatus로 지정된 변수의 index값으로 0부터 증가하여 고유의 번호들을 지정해준다.
- 72 줄
var gfv_count 가 위에서 index값과 동일한 기능을 한다. 파일들의 개수를 계산하여 +1하여 중복을 피하였다.
- 95~105 줄
글쓰기 부분에서 추가하는 부분과 비슷한 방식이지만 고유 번호를 위한 gfv_count의 빈도 수가 많다보니 조금은 복잡해 보인다.
후에 다른 것은 크게 주목할 것들은 없다. 이후 테스트를 해보자.
3. 테스트
3.1 파일을 수정
- 위에 파일 하나만 수정하고 수정하여 보았다.
- 정상적으로 잘 처리 된다.
- 로그의 경우 modifyBoard로 게시물의 제목과 내용을 수정하고 deleteFile로 해당 파일들을 전부 삭제 한 뒤, insertFile로 수정한 파일을 새로 추가하고 기존에 그대로 남아 있는 파일을 복구 시키는 updateFile을 볼수 있다.
3.2 파일을 모두 삭제
- 파일을 모두 삭제하고 수정해보았다.
- 모두 삭제된 결과를 볼 수 있다.
- modifyBoard와 deleteFile 처리만 일어난 것을 볼 수 있다.
3.3 파일이 없는 게시물에 파일 추가
- 파일이 비어있던 게시물에 파일을 2개 추가하여 수정하여 보았다.
- 정상적으로 완료된 것을 볼 수 있다.
- modifyBoard가 실행되고 아무것도 없지만 deleteFile도 실행된 뒤 insertFile이 2개가 실행됨을 볼 수 있다. deleteFile에 대한 것은 스스로 조건을 달아 파일이 없는 경우에는 실행되지 않도록 만들어보도록 하고 여기까지 따라왔으면 잘 따라왔다는 것이다.
'코딩 > Spring' 카테고리의 다른 글
Spring 개발 - 게시판 만들기(17) - 페이징(전자정부프레임워크 라이브러리) (0) | 2018.03.03 |
---|---|
Spring 개발 - 게시판 만들기(16) - AOP 적용하기 (0) | 2018.03.02 |
Spring 개발 - 게시판 만들기(14) - 파일 다운로드 (0) | 2018.01.01 |
Spring 개발 - 게시판 만들기(13) - 파일 업로드 (1) | 2017.12.30 |
Spring 개발 - 게시판 만들기(12) - 게시글 수정하기, 삭제하기 (1) | 2017.12.26 |