이부분은 꽤 복잡해서 뒤쪽에 전체적인 흐름을 적어놨습니다.. 궁금하신분은 한번 읽어보시고 글을 읽는 것을 추천해드립니다.
Mapper에 추가
<update id="updateFile" parameterType="hashMap">
UPDATE MP_FILE SET
DEL_GB = 'Y'
WHERE FILE_NO = #{FILE_NO}
</update>
update문이지만 삭제여부를 판별하는 DEL_GB로 기본값인 'N'을 Y로 바꿔서 화면에 표시가 되지 않도록 한다.
데이터베이스에는 그대로 남아있다.
데이터베이스까지 아예 삭제하고싶다면
delete문을 이용해서 FILE_NO를 이용해 삭제하는 방식을 사용하면 되겠다.
ex)
delete from MP_FILE where FILE_NO = #{FILE_NO}
파라미터를 받아오는 방식은 동일하니 어렵지 않다.
이번글에선 다중첨부파일과 게시글 수정, 삭제를 구현하는데
다중파일첨부를 먼저 구현해보겠다.
글작성 페이지
function fn_addFile(){
var fileIndex = 1;
$(".fileAdd_btn").on("click", function(){
$("#fileIndex").append("<div><input type='file' style='float:left;' name='file_"+(fileIndex++)+"'>"+"</button>"+"<button type='button' style='float:right;' id='fileDelBtn'>"+"삭제"+"</button></div>");
});
$(document).on("click","#fileDelBtn", function(){
$(this).parent().remove();
});
}
다중파일 업로드를 위해 글작성페이지의 script 태그 사이에 해당 코드를 넣어준다.
그 후
<button class="fileAdd_btn" type="button">파일추가</button>
파일추가버튼을 추가
이제 다중 업로드가 가능하다.
DAO
BoardDAO.java
public void updateFile(Map<String, Object> map) throws Exception;
BoardDAOImpl.java
@Override
public void updateFile(Map<String, Object> map) throws Exception {
sqlSession.update("boardMapper.updateFile", map);
}
update문에 필요한 파라미터인 FILE_NO를 map형태로 매개변수로 받고 mapper에 전달한다.
service
BoardService.java
public void update(BoardVO boardVO, String[] files, String[] fileNames, MultipartHttpServletRequest mpRequest) throws Exception;
BoardServiceImpl.java
@Override
public void update(BoardVO boardVO, String[] files, String[] fileNames, MultipartHttpServletRequest mpRequest) throws Exception {
dao.update(boardVO);
List<Map<String, Object>> list = fileUtils.parseUpdateFileInfo(boardVO, files, fileNames, mpRequest);
Map<String, Object> tempMap = null;
int size = list.size();
for(int i = 0; i<size; i++) {
tempMap = list.get(i);
if(tempMap.get("IS_NEW").equals("Y")) {
dao.insertFile(tempMap);
}else {
dao.updateFile(tempMap);
}
}
}
첨부파일 작성이 글작성로직에 들어가듯, 첨부파일 수정 역시 글수정로직에 들어간다.
글수정 서비스 로직에 첨부파일 수정로직인 dao.insertFile , dao.updateFile을 넣어준다.
뒤에 fileUtils에 update 로직을 만들고, tempMap에 IS_NEW 파라미터로 라는 글수정페이지에서 첨부파일이
삭제인지(update), 추가인지(insertFile) 구분한다.
public List<Map<String, Object>> parseUpdateFileInfo(BoardVO boardVO, String[] files, String[] fileNames, MultipartHttpServletRequest mpRequest) throws Exception{
Iterator<String> iterator = mpRequest.getFileNames();
MultipartFile multipartFile = null;
String originalFileName = null;
String originalFileExtension = null;
String storedFileName = null;
List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();
Map<String, Object> listMap = null;
int bno = boardVO.getBno();
while(iterator.hasNext()){
multipartFile = mpRequest.getFile(iterator.next());
if(multipartFile.isEmpty() == false){
originalFileName = multipartFile.getOriginalFilename();
originalFileExtension = originalFileName.substring(originalFileName.lastIndexOf("."));
storedFileName = getRandomString() + originalFileExtension;
multipartFile.transferTo(new File(filePath + storedFileName));
listMap = new HashMap<String,Object>();
listMap.put("IS_NEW", "Y");
listMap.put("BNO", bno);
listMap.put("ORG_FILE_NAME", originalFileName);
listMap.put("STORED_FILE_NAME", storedFileName);
listMap.put("FILE_SIZE", multipartFile.getSize());
list.add(listMap);
}
}
if(files != null && fileNames != null){
for(int i = 0; i<fileNames.length; i++) {
listMap = new HashMap<String,Object>();
listMap.put("IS_NEW", "N");
listMap.put("FILE_NO", files[i]);
list.add(listMap);
}
}
return list;
}
다음은 FileUtils.java파일에 수정메서드를 추가해준다.
원래 게시글을 찾기위해 Bno를 구해주고
multipartFile이 비어있다면 새로운 첨부파일이 등록되었을때 타게 된다. (service 부분)
그 아래엔 files와 fileNames가 null이 아니면 for문을 타게되어
삭제할 파일의 파일번호와 이름을 받게 된다.
첨부파일 수정은 삭제와 삽입만이 있기 때문이다.
수정페이지 뷰 script 부분
function fn_addFile(){
var fileIndex = 1;
$("#fileAdd_btn").on("click", function(){
$("#fileIndex").append("<div><input type='file' style='float:left;' name='file_"+(fileIndex++)+"'>"+"</button>"+"<button type='button' style='float:right;' id='fileDelBtn'>"+"삭제"+"</button></div>");
});
$(document).on("click","#fileDelBtn", function(){
$(this).parent().remove();
});
}
var fileNoArry = new Array();
var fileNameArry = new Array();
function fn_del(value, name){
fileNoArry.push(value);
fileNameArry.push(name);
$("#fileNoDel").attr("value", fileNoArry);
$("#fileNameDel").attr("value", fileNameArry);
}
수정페이지 뷰 form태그 사이에 input hidden
<input type="hidden" id="fileNoDel" name="fileNoDel[]" value="">
<input type="hidden" id="fileNameDel" name="fileNameDel[]" value="">
수정페이지 뷰의 파일추가/삭제 부분
<td id="fileIndex">
<c:forEach var="file" items="${file}" varStatus="var">
<div>
<input type="hidden" id="FILE_NO" name="FILE_NO_${var.index}" value="${file.FILE_NO }">
<input type="hidden" id="FILE_NAME" name="FILE_NAME" value="FILE_NO_${var.index}">
<a href="#" id="fileName" onclick="return false;">${file.ORG_FILE_NAME}</a>(${file.FILE_SIZE}kb)
<button id="fileDel" onclick="fn_del('${file.FILE_NO}','FILE_NO_${var.index}');" type="button">삭제</button><br>
</div>
</c:forEach>
</td>
</tr>
</tbody>
</table>
</div>
<div style="margin-left:1px;">
<button type="button" id="update_btn" class="btn btn-primary" >수정</button>
<button type="button" id="cancel_btn" class="btn btn-primary">취소</button>
<button type="button" id="fileAdd_btn" class="btn btn-primary">파일추가</button>
컨트롤러 부분
// 게시판 수정뷰
@RequestMapping(value = "/updateView", method = RequestMethod.GET)
public String updateView(BoardVO boardVO, @ModelAttribute("scri") SearchCriteria scri, Model model)
throws Exception {
logger.info("updateView");
model.addAttribute("update", service.read(boardVO.getBno()));
model.addAttribute("scri", scri);
List<Map<String, Object>> fileList = service.selectFileList(boardVO.getBno());
model.addAttribute("file", fileList);
return "board/updateView";
}
// 게시판 수정
@RequestMapping(value = "/update", method = RequestMethod.POST)
public String update(BoardVO boardVO,
@ModelAttribute("scri") SearchCriteria scri,
RedirectAttributes rttr,
@RequestParam(value="fileNoDel[]") String[] files,
@RequestParam(value="fileNameDel[]") String[] fileNames,
MultipartHttpServletRequest mpRequest) throws Exception {
logger.info("update");
service.update(boardVO, files, fileNames, mpRequest);
rttr.addAttribute("page", scri.getPage());
rttr.addAttribute("perPageNum", scri.getPerPageNum());
rttr.addAttribute("searchType", scri.getSearchType());
rttr.addAttribute("keyword", scri.getKeyword());
return "redirect:/board/list";
}
게시글 상세보기 페이지에서 수정버튼을 클릭하면 첨부파일을 수정할 수 있도록, 수정뷰url을 호출한다.(updateView)
컨트롤러에서 수정뷰 url이 잡히면 게시글PR로 파일조회메서드를 실행해서 model로 첨부파일의 파라미터를 불러, 뷰단으로 값을 보낸다.(file이라는 이름의 key와 fileList 라는 value) (뷰단인 updateView로 리턴)
수정뷰에선 ${file.~}로 fileList의 파라미터를 불러와서 게시글의 원래 파일을 불러올 수 있다.
파일추가 버튼을 누르면 script의 id="fileAdd_btn"인 #fileAdd_btn 함수를 호출하고,
다중 파일이 추가된다. 이것은 가장위의 글작성페이지의 파일추가와 같다.
삭제버튼을 누르면 fn_del 함수를 호출하고, 해당파일의 ${file.FILE_NO} 와 FILE_NO_${var.index} 를 파라미터로 보내,
그 값을 push를 이용해서 fileNoDel과 fileNameDel에 담아준다. (String[] 의 형태)
두 값을 input태그에 넣어서 뷰단으로 submit 한다 ( 수정뷰 -> 수정버튼 클릭)
아무래도 다중파일첨부에 수정, 삭제는 좀 얽혀있는 느낌이 있어서 로직을 뷰-컨트롤러-모델 순으로 쭉 정리해보려고 한다.
---------------
게시글 상세페이지(게시글내용)에서 수정버튼을 클릭 - 수정뷰 컨트롤러를 호출한다. (매핑주소 /updateView)
상세페이지에서 수정뷰로 넘어올때 넘겨받은 파라미터 boardVO로 Bno(게시글PR)를 얻어서 파일리스트조회 service에 매개변수로 주고, 파일리스트를 가져온다.
Map에 담겨있는 파일데이터들의 List를 fileList에 담고,Model 객체를 이용해서 file이라는 이름의 key에 담아서 ,
수정뷰(board/updateView.jsp) 로 리턴한다.
그 후 수정뷰에 파일을 표시하는 <td id="fileIndex">에서 방금 컨트롤러에서 받은 Model객체를 이용,
file.~ 로 원래 추가되어 있던 파일의 파라미터를 받아 화면을 구성한다.
이제 수정뷰가 완료 됐다.
파일을 추가하기 위해 파일추가버튼을 누르면 fileAdd js함수가호출되고, 인덱스가 1씩 추가되며 input type = file이 추가된다. 다중파일은 하나의 FILE_NO에 인덱스 번호로 구분하여 추가와 삭제를 한다.
추가 된 파일은 수정버튼을 눌러서 모델에 insert 시키기 전엔 remove()함수를 사용해서 간단히 취소할 수 있다.
파일을 삭제하기위해 삭제버튼을 누르면 fn_del js함수를 호출시켜서, 파일번호와 파일인덱스를
fileNoDel 과 fileNameDel에 String[] 형식으로 push() 함수로 넣고 그 값을 input hidden에 담아서 값을 컨트롤러로 보낸다.
이제 수정버튼을 누른다.
컨트롤러의 /update를 호출한다.
수정뷰에서 파라미터로 받은 BoardVO(게시글PR), files (파일번호) , fileNames(파일인덱스) , mpRequest(첨부파일값)를 매개변수로 선언해주고, update service로 파라미터를 보낸다.
Service에서
자세히 볼 것은 dao.inserFIle과 updateFile이다. dao.update는 게시글 내용 수정에 대한 부분이고,
아까 추가한 fileUtils에 파라미터로 받은 값들을 매개변수로 넣어준다.
여기서 수정뷰에서 받아온 값들이 추가인지 삭제인지 구별한다.
받아온 mpRequest의 값이 비어있지 않다면 (isEmpty) 첨부파일을 추가한 것이기 때문에,
VO에서 Bno(게시글PR) 을 get으로 가져와서 게시글을 특정하고, 첨부파일 insert에 필요한 값들을 put으로 listMap에 넣고 "IS_NEW"에"Y"를 넣어준다.
만약 받아온 mpRequest값이 비어있고 파일번호와 파일인덱스 값이 null이 아니면 수정뷰에서 '삭제'버튼이 눌린 첨부파일의 값이 넘어온 것이기 때문에 IS_NEW에 N을 넣고 FILE_NO에 파일번호[인덱스 수 만큼] 을 담는다.
이걸 인덱스 수 만큼 반복해서 파일번호[1] , 파일번호[4] 이 값을 Map에 담는것이 삭제 로직이다.
아까 다중파일 첨부를 했을때 파일을 추가할때마다 파일번호에 인덱스 번호가 +1씩 붙도록 뷰를 만들었다.
그래서 파일은 파일번호[1] 2 3 4 5 이런식으로 저장이 되어, 삭제할때도 파일번호와 인덱스번호를 가져와서
반복문으로 처리하는 것이다.
이제 service로 돌아와서 이렇게 처리 된 값을 구분하는건 IS_NEW이다 이다 , fileUtils에선 값이 비어있는지 아닌지 확인해서 Y와 N을 각각 put 해줬다. 여기선 list의 개수만큼 반복해서 다중파일을 처리하고,
IS_NEW에 Y가 있으면 dao.insertFile을 , N이 있으면 updateFile을 호출해서 적용한다.
첨부파일의 수정과 삭제는 update와 delete가 아니라 insert와 update를 이용한다는 것을 기억하자. (물론 delete도 사용가능하긴 함 로직은 비슷하다)
그 다음 DAO와 mapper 부분은 간단하니 설명은 생략하겠다..
이 로직을 이해하는데 상당히 어려웠다;;
알려주는 사람 없이 먼저 구현하고 한줄한줄 읽으며 로직을 이해하려고 애썻다.
혼자 공부 기록용으로 작성했기 때문에 내가 알아볼 수 있는 수준으로 글을 작성해서 읽기는 힘들 수 있을거라고 예상합니다. 혹시 궁금한 부분이나 전체적인 코드가 궁금하다면 댓글을 남겨주세요.
'SPRING > IceWater Community' 카테고리의 다른 글
[스프링] ajax 댓글 에디터 및 이미지 삽입 구현 (0) | 2021.09.02 |
---|---|
[스프링]게시판 글작성 에디터 적용과 파일미리보기,이미지 미리보기 (0) | 2021.08.30 |
[스프링]게시판 첨부파일 다운로드 (3) (1) | 2021.08.30 |
[스프링] 게시글 첨부파일 조회 구현 설명(select) (2) (1) | 2021.08.30 |
[스프링]게시판 첨부파일 업로드 구현 설명(insert) (1) (1) | 2021.08.30 |