MelonPeach

27. 스프링 게시판 만들기 / 첨부파일 업로드, 다운로드(4)

 

안녕하세요

이전 포스팅에서 파일 다운로드까지 진행했었는데요.

이번에는 다중 첨부파일과 첨부파일 수정, 삭제를 만들어보겠습니다.

 

 

 

1. boardMapper.xml 작성


스프링 다중 파일첨부(boardMapper.xml)

 

글 작성 페이지에서 파일을 등록하거나 글 수정 페이지에서 파일을 수정 또는 삭제할때 파일이

등록된 순서대로 조회할 수 있도록 ORDER BY FILE_NO ASC를 추가해줍니다.

그리고 조건에 AND DEL_GN = 'N'이 추가되었습니다.

 

UPDATE쿼리를 이용하여 삭제한 파일은 DEL_GB를 Y로 바꿉니다.

여기서 파일을 실제로 삭제하는것이 아니라 DEL_GB에 따라 N값이면 보여주고 Y이면 보여주지 않는 식으로 진행하려고 합니다.

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="boardMapper">
	<!-- 게시판 글 작성 -->
	<insert id="insert" parameterType="kr.co.vo.BoardVO" useGeneratedKeys="true" keyProperty="bno">
	    <selectKey keyProperty="bno" resultType="int" order="BEFORE">
	    	SELECT MP_BOARD_SEQ.NEXTVAL FROM DUAL
	    </selectKey>
	    INSERT INTO MP_BOARD(    BNO 
	   					       , TITLE 
	   					       , CONTENT 
	   					       , WRITER  )
	                  VALUES(    #{bno}
	                 		   , #{title}
	                 		   , #{content}
	                 		   , #{writer}  )
   
    </insert>
   
	<select id="listPage" resultType="kr.co.vo.BoardVO" parameterType="kr.co.vo.SearchCriteria">
		SELECT  BNO, 
		        TITLE, 
		        CONTENT,
		        WRITER, 
		        REGDATE
		 FROM ( 
		        SELECT BNO, 
		               TITLE, 
		               CONTENT, 
		               WRITER, 
		               REGDATE, 
		               ROW_NUMBER() OVER(ORDER BY BNO DESC) AS RNUM
		         FROM MP_BOARD
		         WHERE 1=1 
		         	<include refid="search"></include>
		                       ) MP
		WHERE RNUM BETWEEN #{rowStart} AND #{rowEnd}
		ORDER BY BNO DESC
	</select>
	
	<select id="listCount" parameterType="kr.co.vo.SearchCriteria" resultType="int">
		SELECT COUNT(BNO)
		   FROM MP_BOARD
		   WHERE 1=1
		<include refid="search"></include>	
		   AND BNO > 0
	</select>
	
	<sql id="search">
		<if test="searchType != null">
			<if test="searchType == 't'.toString()">AND TITLE LIKE '%' || #{keyword} || '%'</if>
			<if test="searchType == 'c'.toString()">AND CONTENT LIKE '%' || #{keyword} || '%'</if>
			<if test="searchType == 'w'.toString()">AND WRITER LIKE '%' || #{keyword} || '%'</if>
			<if test="searchType == 'tc'.toString()">AND (TITLE LIKE '%' || #{keyword} || '%') or (CONTENT LIKE '%' || #{keyword} || '%')</if>
		</if>
	</sql>
	
	<select id="read" parameterType="int" resultType="kr.co.vo.BoardVO">
		SELECT   BNO
			   , TITLE
			   , CONTENT
			   , WRITER
			   , REGDATE
		  FROM MP_BOARD
		 WHERE BNO = #{bno}
	</select>
	
	<update id="update" parameterType="kr.co.vo.BoardVO">
		UPDATE MP_BOARD
		   SET TITLE    =   #{title},
		   	   CONTENT  =   #{content}
		 WHERE BNO = #{bno} 
	</update>
	
	<delete id="delete" parameterType="int">
		DELETE 
		  FROM MP_BOARD
		 WHERE BNO = #{bno}
	</delete>
   
   <!-- 첨부파일 업로드 -->
    <insert id="insertFile" parameterType="hashMap">
		INSERT INTO MP_FILE(
			FILE_NO,
			BNO,
			ORG_FILE_NAME,
			STORED_FILE_NAME,
			FILE_SIZE
		)VALUES(
			SEQ_MP_FILE_NO.NEXTVAL,
			#{BNO},
			#{ORG_FILE_NAME},
			#{STORED_FILE_NAME},
			#{FILE_SIZE}
		)
    </insert>
    
    <!-- 첨부파일 조회 -->
    <select id="selectFileList" parameterType="int" resultType="hashMap">
    	SELECT FILE_NO,
    		   ORG_FILE_NAME,
    		   ROUND(FILE_SIZE/1024,1) AS FILE_SIZE,
    		   DEL_GB
    	  FROM MP_FILE
    	 WHERE BNO = #{BNO}
    	   AND DEL_GB = 'N'
    	 ORDER BY FILE_NO ASC
    </select>
    
    <!-- 첨부파일 다운 -->
    <select id="selectFileInfo" parameterType="hashMap" resultType="hashMap">
    	SELECT 
    		STORED_FILE_NAME,
    		ORG_FILE_NAME
    	FROM MP_FILE
    	WHERE FILE_NO = #{FILE_NO}
    </select>
    
    <update id="updateFile" parameterType="hashMap">
    	UPDATE MP_FILE SET
    	DEL_GB = 'Y'
    	WHERE FILE_NO = #{FILE_NO}
    </update>
</mapper>

 

 

 

2. BoardDAO작성


스프링 다중 파일첨부(BoardDAO.java)

 

Map형태의 파라미터(map)를 받아 보내줍니다.

map는 FILE_NO가 있겠지요.

 

BoardDAO.java
	// 첨부파일 수정
	public void updateFile(Map<String, Object> map) throws Exception;
    

BoardDAOImpl.java
	@Override
	public void updateFile(Map<String, Object> map) throws Exception {
		// TODO Auto-generated method stub
		
		sqlSession.update("boardMapper.updateFile", map);
	}

 

 

 

3. BoardService작성


스프링 다중 파일첨부(BoardService.java)

 

게시물 수정 메소드에서 파라미터들을 추가해줍니다.

BoardServiceImpl.java를 보시면

64번줄은 게시물을 업데이트하는 쿼리이고

 

66번줄 : 파일 업데이트할 값들을 list에 담습니다.

69번줄 : fileUtils.parseUpdateFileInfo()결과의 size만큼  for문을 돌립니다.

70~75번줄 : tempMap에 list.get(i)를 담고 if문을 이용하여 tempMap에서 IS_NEW를 꺼내와서 값이Y와 같으면

dao.insertFile(tempMap)를 실행합니다. 같지 않으면 dao.updateFile(tempMap)실행합니다.

 

 

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);
			}
		}
	}

 

 

 

4. FileUtils작성


스프링 다중 파일첨부(FileUtils.java)

 

스프링 다중 파일첨부(FileUtils.java)

parseUpdateFileInfo메소드를 작성해줍니다.

74번줄 : if(multipartFile.isEmpty() == false)

multipartFile이 비어있지 않으면 if문을 타게되는데

새로운 새로운 첨부파일이 등록되었을때 타게됩니다.

 

88번줄 : files와 fileNames가 null이 아니면 for문을 타게됩니다.

이 for문은 삭제할 파일의 파일 번호와 파일이름을 받게되어있습니다.

 

package kr.co.util;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import kr.co.vo.BoardVO;

@Component("fileUtils")
public class FileUtils {
	private static final String filePath = "C:\\mp\\file\\"; // 파일이 저장될 위치
	
	public List<Map<String, Object>> parseInsertFileInfo(BoardVO boardVO, 
			MultipartHttpServletRequest mpRequest) throws Exception{
		
		/*
			Iterator은 데이터들의 집합체? 에서 컬렉션으로부터 정보를 얻어올 수 있는 인터페이스입니다.
			List나 배열은 순차적으로 데이터의 접근이 가능하지만, Map등의 클래스들은 순차적으로 접근할 수가 없습니다.
			Iterator을 이용하여 Map에 있는 데이터들을 while문을 이용하여 순차적으로 접근합니다.
		*/
		
		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();
		
		File file = new File(filePath);
		if(file.exists() == false) {
			file.mkdirs();
		}
		
		while(iterator.hasNext()) {
			multipartFile = mpRequest.getFile(iterator.next());
			if(multipartFile.isEmpty() == false) {
				originalFileName = multipartFile.getOriginalFilename();
				originalFileExtension = originalFileName.substring(originalFileName.lastIndexOf("."));
				storedFileName = getRandomString() + originalFileExtension;
				file = new File(filePath + storedFileName);
				multipartFile.transferTo(file);
				listMap = new HashMap<String, Object>();
				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);
			}
		}
		return list;
	}
	
	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; 
	}

	
	public static String getRandomString() {
		return UUID.randomUUID().toString().replaceAll("-", "");
	}
}

 

 

 

5. BoardController작성


스프링 다중 파일첨부(BoardController.java)

 

98,99번줄 : updateView페이지에 파일리스트가 보이게 추가해줍니다.

105번줄 : 파라미터에 @RequestParam이 붙은 fileNoDel[]과 fileNameDel[]은 jsp에서 fileNoDel[]과 fileNameDel[]로 지정한 값을 String[] 타입으로 담겠다는 말입니다.

그럼 이 값들을 112번줄 메소드에 파라미터값이 전달되도록 수정해줍니다.

 

	// 게시판 수정뷰
	@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";
	}

 

 

 

6. writeView작성


스프링 다중 파일첨부(writeView.jsp)

 

스프링 다중 파일첨부(writeView.jsp)

 

writeView를 조금 수정해줬습니다.

 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<html>
	<head>
		<!-- 합쳐지고 최소화된 최신 CSS -->
		<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
		<!-- 부가적인 테마 -->
		<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
	 	
	 	<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
	 	
	 	<title>게시판</title>
	</head>
	<script type="text/javascript">
		$(document).ready(function(){
			var formObj = $("form[name='writeForm']");
			$(".write_btn").on("click", function(){
				if(fn_valiChk()){
					return false;
				}
				formObj.attr("action", "/board/write");
				formObj.attr("method", "post");
				formObj.submit();
			});
			fn_addFile();
		})
		function fn_valiChk(){
			var regForm = $("form[name='writeForm'] .chk").length;
			for(var i = 0; i<regForm; i++){
				if($(".chk").eq(i).val() == "" || $(".chk").eq(i).val() == null){
					alert($(".chk").eq(i).attr("title"));
					return true;
				}
			}
		}
		function fn_addFile(){
			var fileIndex = 1;
			//$("#fileIndex").append("<div><input type='file' style='float:left;' name='file_"+(fileIndex++)+"'>"+"<button type='button' style='float:right;' id='fileAddBtn'>"+"추가"+"</button></div>");
			$(".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>
	<body>
	
		<div id="root">
			<header>
				<h1> 게시판</h1>
			</header>
			<hr />
			 
			<div>
				<%@include file="nav.jsp" %>
			</div>
			<hr />
			
			<section id="container">
				<form name="writeForm" method="post" action="/board/write" enctype="multipart/form-data">
					<table>
						<tbody>
							<c:if test="${member.userId != null}">
								<tr>
									<td>
										<label for="title">제목</label><input type="text" id="title" name="title" class="chk" title="제목을 입력하세요."/>
									</td>
								</tr>	
								<tr>
									<td>
										<label for="content">내용</label><textarea id="content" name="content" class="chk" title="내용을 입력하세요."></textarea>
									</td>
								</tr>
								<tr>
									<td>
										<label for="writer">작성자</label><input type="text" id="writer" name="writer" class="chk" title="작성자를 입력하세요." value="${member.userId}" />
									</td>
								</tr>
								<tr>
								<tr>
									<td id="fileIndex">
									</td>
								</tr>
								<tr>
									<td>						
										<button class="write_btn" type="submit">작성</button>	
										<button class="fileAdd_btn" type="button">파일추가</button>	
									</td>
								</tr>	
							</c:if>
							<c:if test="${member.userId == null}">
								<p>로그인 후에 작성하실 수 있습니다.</p>
							</c:if>
							
						</tbody>			
					</table>
				</form>
				
			</section>
			<hr />
		</div>
	</body>
</html>

 

 

 

7. updateView작성


스프링 다중 파일첨부(updateView.jsp)

 

스프링 다중 파일첨부(writeView.jsp)

 

스프링 다중 파일첨부(writeView.jsp)

 

삭제버튼을 눌렀을때 파일의 번호와 파일의 인덱스를 파라미터로 fn_del()함수에 보내줍니다.

push를 이용하여 삭제한 번호를 담아줍니다.

담은 파일을 94, 95번줄 input태그에 값을 담아줍니다.

 

 

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<html>
	<head>
		<!-- 합쳐지고 최소화된 최신 CSS -->
		<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
		<!-- 부가적인 테마 -->
		<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
	 	
	 	<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
	 	
	 	<title>게시판</title>
	</head>
	<script type="text/javascript">
		$(document).ready(function(){
			var formObj = $("form[name='updateForm']");
			
			$(document).on("click","#fileDel", function(){
				$(this).parent().remove();
			})
			
			fn_addFile();
			
			$(".cancel_btn").on("click", function(){
				event.preventDefault();
				location.href = "/board/readView?bno=${update.bno}"
					   + "&page=${scri.page}"
					   + "&perPageNum=${scri.perPageNum}"
					   + "&searchType=${scri.searchType}"
					   + "&keyword=${scri.keyword}";
			})
			
			$(".update_btn").on("click", function(){
				if(fn_valiChk()){
					return false;
				}
				formObj.attr("action", "/board/update");
				formObj.attr("method", "post");
				formObj.submit();
			})
		})
			
		function fn_valiChk(){
			var updateForm = $("form[name='updateForm'] .chk").length;
			for(var i = 0; i<updateForm; i++){
				if($(".chk").eq(i).val() == "" || $(".chk").eq(i).val() == null){
					alert($(".chk").eq(i).attr("title"));
					return true;
				}
			}
		}
 		function fn_addFile(){
			var fileIndex = 1;
			//$("#fileIndex").append("<div><input type='file' style='float:left;' name='file_"+(fileIndex++)+"'>"+"<button type='button' style='float:right;' id='fileAddBtn'>"+"추가"+"</button></div>");
			$(".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);
 		}
	</script>
	<body>
	
		<div id="root">
			<header>
				<h1> 게시판</h1>
			</header>
			<hr />
			 
			<div>
				<%@include file="nav.jsp" %>
			</div>
			<hr />
			
			<section id="container">
				<form name="updateForm" role="form" method="post" action="/board/update" enctype="multipart/form-data">
					<input type="hidden" name="bno" value="${update.bno}" readonly="readonly"/>
					<input type="hidden" id="page" name="page" value="${scri.page}"> 
					<input type="hidden" id="perPageNum" name="perPageNum" value="${scri.perPageNum}"> 
					<input type="hidden" id="searchType" name="searchType" value="${scri.searchType}"> 
					<input type="hidden" id="keyword" name="keyword" value="${scri.keyword}"> 
					<input type="hidden" id="fileNoDel" name="fileNoDel[]" value=""> 
					<input type="hidden" id="fileNameDel" name="fileNameDel[]" value=""> 
					<table>
						<tbody>
							<tr>
								<td>
									<label for="title">제목</label><input type="text" id="title" name="title" value="${update.title}" class="chk" title="제목을 입력하세요."/>
								</td>
							</tr>	
							<tr>
								<td>
									<label for="content">내용</label><textarea id="content" name="content" class="chk" title="내용을 입력하세요."><c:out value="${update.content}" /></textarea>
								</td>
							</tr>
							<tr>
								<td>
									<label for="writer">작성자</label><input type="text" id="writer" name="writer" value="${update.writer}" readonly="readonly"/>
								</td>
							</tr>
							<tr>
								<td>
									<label for="regdate">작성날짜</label>
									<fmt:formatDate value="${update.regdate}" pattern="yyyy-MM-dd"/>					
								</td>
							</tr>
							<tr>
								<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>
						<button type="button" class="update_btn">저장</button>
						<button type="button" class="cancel_btn">취소</button>
						<button type="button" class="fileAdd_btn">파일추가</button>
					</div>
				</form>
			</section>
			<hr />
		</div>
	</body>
</html>

 

 

 

8. 실행테스트


스프링 다중 파일첨부(실행테스트)

 


프로젝트 첨부파일 입니다.

study.z01
7.00MB
study.z02
7.00MB
study.zip
4.06MB

이 글을 공유합시다

facebook twitter googleplus kakaostory naver