본 내용은 혼자 공부하기 위하여 다른 포스트를 보면서 저에게 필요한 부분과 궁금했던 부분을 추가하여 게시하는 곳입니다.
궁금한 것에 대한 것은 모르는 것이 많겠지만 함께 알아보도록 노력하겠습니다.
참조 게시 포스트 : http://addio3305.tistory.com/
------------------------------------------------------------------------------------------------------------------------------------------
1. 파일 다중 업로드
1.1 JSP
파일 다중 업로드를 위해 jsp 파일을 수정하겠다.
boardWrite.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 | <%@ 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 > < 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @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
1 2 3 4 5 6 7 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | < 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
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 | 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
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 | <%@ 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 |