파일업로드 방법 정리 순서
1. Form 태그 자체로 전송하기
2. jQeury ajax form 전송
3. Ajax submit
4. Ajax Form submit : jQuery plugin 사용
소스코드 순서
1. pom.xml
2.context-common.xml
3. jsp
4. controller
5. service
6. CommonUtils
7. FileUtils
8. DAO
9. SQL
[화면설명]
1.기본화면
2.파일선택
3. jQeury ajax form test 결과
4. Ajax submit test 결과
5. Ajax Form submit test 결과
[소스코드 설명]
1.Maven Dependency 추가 : pom.xml
<!-- MultipartHttpServletRequset -->
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<!-- fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.2</version>
</dependency>
2.context-common.xml 작성
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
<!-- MultipartResolver 설정 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="100000000" />
<property name="maxInMemorySize" value="100000000" />
</bean>
</beans>
3.JSP
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>File Upload Test</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/js/bootstrap.min.js"></script>
<script src="/resources/lib/jquery.form.min.js"></script>
<script src="/resources/lib/expansion.js"></script>
<script src="/resources/js/excel-download.js"></script>
</head>
<body>
<div class="container">
<h3>File Upload Test</h3>
<form id="frm-upload" enctype="multipart/form-data" action="/file/upload" method="post">
<div class="form-group">
<label for="exampleInputFile">File input</label>
<input type="file" id="test-file" name="file">
</div>
<div class="form-group">
<input type="file" multiple="multiple" name="file">
</div>
<div class="form-group">
<input type="text" name="userName" placeholder="Enter user name">
</div>
<!-- Form 태그 자체로 전송하기 -->
<button type="button" class="btn btn-primary btn-form-submit">Form Submit</button>
<!-- jquery ajax으로 전송하기 -->
<button type="button" class="btn btn-primary btn-grnl-submit">Form Data Submit</button>
<button type="button" class="btn btn-primary btn-ajax-submit">Ajax Submit</button>
<button type="button" class="btn btn-primary btn-ajaxform-submit">Ajax Form Submit</button>
</form>
</div>
</body>
<script type="text/javascript">
$(document).ready(function() {
/**
* 1.Form 태그 자체로 전송하기
*/
$('.btn-form-submit').on('click', function() {
$('#frm-upload').submit();
});
/**
* jQeury ajax form 전송
*/
$('.btn-grnl-submit').on('click', function() {
var form = $('#frm-upload');
//formdata 생성
var formData = new FormData(form);
formData.append("file", $('input[name=file]')[0].files[0]);
var len = $('input[name="file"]')[1].files.length;
for (var i = 0; i < len; i++) {
formData.append("file" + i, $('input[name=file]')[1].files[i]);
}
$.ajax({
url : '/file/upload',
type : 'POST',
data : formData,
processData : false,
contentType : false,
beforeSend : function() {
console.log('jQeury ajax form submit beforeSend');
},
success : function(data) {
console.log('jQeury ajax form submit success');
},
error : function(data) {
console.log('jQeury ajax form submit error');
},
complete : function() {
console.log('jQeury ajax form submit complete');
}
});//end ajax
});
/**
* Ajax submit
*/
$('.btn-ajax-submit').on('click', function() {
$.ajax({
url : '/file/upload',
type : 'POST',
dataType : 'json',
data : {
file : $('#test-file').val()
},
beforeSend : function() {
console.log('Ajax submit beforeSend');
},
success : function(data) {
console.log('Ajax submit success');
},
error : function(data) {
console.log('Ajax submit error');
},
complete : function() {
console.log('Ajax submit complete');
}
});//end ajax
});
/**
* Ajax Form submit
*/
$('.btn-ajaxform-submit').on('click', function() {
$('#frm-upload').ajaxForm({
beforeSubmit : function(data, form, option) {
console.log("data ::::: ", data);
console.log("form ::::: " , form);
console.log("option ::::: ", option);
//check validation
var username = $('input[name=userName]');
checkName(username);
},
success : function(data) {
//성공후 서버에서 받은 데이터 처리
console.log('Ajax Form submit success');
},
error : function(data) {
//에러발생을 위한 code페이지
console.log('Ajax Form submit error');
}
}).submit();
});
function checkName(username) {
if (username.val() == null || username.val() == '') {
alert('이름을 입력하세요');
username.focus();
return false;
}
return true;
}
});//end document ready
</script>
</html>
파일 업로드시 form 태그에는 파일 인코딩을 설정하여 파일 업로드의 경우 별도의 인코딩을 수행하지 않도록 속성을 추가합니다.
enctype="multipart/form-data"
이는 해당 form이 Multipart 형식임을 알려줍니다.
사진, 동영상 등 글자가 아닌 파일은 모두 Multipart 형식의 데이터 입니다.
만약 위와 같은 속성을 적용하지 않았을 경우에는 웹 서버로 데이터를 전송할 경우 파일의 경로명만 전송되고 파일내용이 전송되지 않습니다.
- action : 데이터를 전송할 때 수행할 작업을 정의합니다.
- method : 데이터를 전송할 때 HTTP 메서드 ( GET, POST )를 지정합니다.
- enctype : 전송한 데이터의 인코딩을 지정합니다. (기본값 : url-encoded)
input 태그에서 파일 다중 선택을 하기위해서는 multiple = "multiple" 속성이 적용되어야 합니다.
이 속성을 사용하였을 경우 파일 탐색기에서 하나 이상의 파일을 선택할 수 있습니다.
(파일 다중 선택이므로 다중 첨부와는 다른 개념이라고 생각해야 합니다.)
파일을 다중선택하여 업로드 할 경우 name의 속성 값이 배열로 전달되므로, 마지막에 배열기호를 추가해야 합니다.
name = "file_name[]"
4.Controller
package component.main.controller;
import java.io.File;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import component.com.resolver.ParamCollector;
import component.main.service.FileService;
@Controller
public class FileUploadController {
private static final Logger log = Logger.getLogger(FileUploadController.class);
@Autowired
FileService fileService;
@RequestMapping(value = "/file", method = RequestMethod.GET)
public ModelAndView getFilePage(ParamCollector inparams) {
ModelAndView mav = new ModelAndView("fileUpload");
return mav;
}
@RequestMapping(value = "/file/upload", method = RequestMethod.POST)
public ModelAndView getFile(ParamCollector inparams, HttpServletRequest request) throws Exception {
ModelAndView mav = new ModelAndView("jsonView");
if (log.isDebugEnabled()) {
log.debug(inparams);
}
fileService.insertFileUpload(inparams.getMap(), request);
return mav;
}
}
파라미터로 HttpServletRequest를 추가로 받습니다.
화면에서 전송한 모든 데이터는 HttpServletRequest에 담겨서 전송되었고 그것을 HandlerMethodArgumentResolver를 이용하여 ParamCollector에 담아주었습니다. ( https://jieun0113.tistory.com/44 )
첨부파일은 ParamCollector에서 처리를 하지 않았기 때문에 HttpServletRequest를 추가로 받도록 하였습니다.
이 HttpServletRequest에는 모든 텍스트 뿐만이 아니라 화면에서 전송된 파일정보도 함께 담겨 있습니다.
ParamCollector를 이용하여 텍스트 데이터를 처리하므로 HttpServletRequest는 파일정보로 활용할 계획입니다.
( Controller는 단순히 웹 클라이언트에서 들어온 요청에 해당하는 비즈니스 로직을 호출하고 응답해주는 Dispatcher 역할만 한다.)
5.service
package component.main.service;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartHttpServletRequest;
public interface FileService {
public void insertFileUpload(Map<String, Object> map, HttpServletRequest request) throws Exception;
}
package component.main.service.impl;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import component.com.utill.FileUtils;
import component.main.dao.FileDao;
import component.main.service.FileService;
@Service("fileService")
public class FileServiceImpl implements FileService {
private static final Logger log = Logger.getLogger(FileServiceImpl.class);
@Resource
private FileUtils fileUtils;
@Resource
private FileDao fileDao;
@Override
public void insertFileUpload(Map<String, Object> map, HttpServletRequest request) throws Exception {
List<Map<String, Object>> list = fileUtils.parseInsertFileInfo(map, request);
for (int i = 0; i < list.size(); i++) {
Map<String, Object> vo = list.get(i);
fileDao.insertFileUpload(vo);
}
//test
MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request;
Iterator<String> iterator = multipartHttpServletRequest.getFileNames();
MultipartFile multipartFile = null;
while (iterator.hasNext()) {
multipartFile = multipartHttpServletRequest.getFile(iterator.next());
if (multipartFile.isEmpty() == false) {
log.debug("---------- file start ----------");
log.debug("name : " + multipartFile.getName());
log.debug("filename : " + multipartFile.getOriginalFilename());
log.debug("size : " + multipartFile.getSize());
log.debug("---------- file end ----------\n");
}
}
}
}
ORIGINAL_FILE_NAME과 STORED_FILE_NAME 컬럼은 각각 원본 파일 이름과 변경된 파일 이름을 저장합니다.
파일을 업로드하면 이 파일은 서버의 어딘가에 저장이 되어야 합니다.
만약 파일 이름이 중복될 경우, 저장 중 문제가 발생하거나 파일 이름이 변경될 수 있습니다.
때문에 파일을 저장할 때, 원본파일의 이름을 저장하고 서버에는 변경된 파일이름으로 파일을 저장합니다.
파일을 다운로드할 시 파일의 이름을 통해서 해당 파일에 접근하기 때문에 겹치지 않는 파일이름은 중요합니다.
ORIGINAL_FILE_NAME은 260byte, STORED_FILE_NAME은 36byte로 잡았습니다.
이유는 windows에서는 "최대 256글자 + 확장자 = 260글자" 이고, STORED_FILE_NAME은 "32글자 + 확장자"로 저장할 계획입니다.
6.CommonUtils
package component.com.utill;
import java.util.UUID;
public class CommonUtils {
public static String getRandomString(){
return UUID.randomUUID().toString().replaceAll("-", "");
}
}
7.FileUtils
package component.com.utill;
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 javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
@Component("fileUtils")
public class FileUtils {
private static final String filePath = "C:\\dev\\upload\\";
public static boolean deleteFile(String storedFileName) {
File file = new File(filePath + storedFileName);
//upload가 있는지 확인
//있을때만 작업한다
if(file.exists()){
if(file.delete()) {
return true;
}
}
return false;
}
public static List<Map<String,Object>> parseInsertFileInfo(Map<String,Object> map, HttpServletRequest request) throws Exception{
MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest)request;
Iterator<String> iterator = multipartHttpServletRequest.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;
// String boardIdx = (String)map.get("IDX");
File file = new File(filePath);
//경로가 존재하지 않을 경우 디렉토리를 만든다.
if(file.exists() == false){
file.mkdirs();
}
while(iterator.hasNext()){
multipartFile = multipartHttpServletRequest.getFile(iterator.next());
if(multipartFile.isEmpty() == false){
//업로드한 파일의 확장자를 포함한 파일명이다.
originalFileName = multipartFile.getOriginalFilename();
//업로드한 파일의 마지막 .을 포함한 확장자를 substring 한 것.
originalFileExtension = originalFileName.substring(originalFileName.lastIndexOf("."));
//32자리의 숫자를 포함한 랜덤 문자열 + 확장자를 붙여준 파일명이다.
storedFileName = CommonUtils.getRandomString() + originalFileExtension;
file = new File(filePath + storedFileName);
multipartFile.transferTo(file);
listMap = new HashMap<String,Object>();
// listMap.put("BOARD_IDX", boardIdx);
//업로드할 당시의 파일이름
listMap.put("ORIGINAL_FILE_NAME", originalFileName);
//저장할 파일 이름
listMap.put("STORED_FILE_NAME", storedFileName);
listMap.put("FILE_SIZE", multipartFile.getSize());
list.add(listMap);
}
}
return list;
}
}
8. DAO
package component.main.dao;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Repository;
import component.com.dao.AbstractDAO;
@Repository
public class FileDao extends AbstractDAO {
public void insertFileUpload(Map<String, Object> map) {
insert("file.insertFileUpload", map);
}
}
9. SQL
<?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="file">
<insert id="insertFileUpload" parameterType="hashmap">
INSERT INTO TB_FILE
(
IDX
, ORIGINAL_FILE_NAME
, STORED_FILE_NAME
, FILE_SIZE
, DEL_YN
, REG_DTM
, REG_ID
, UPD_DTM
, UPD_ID
) VALUES
(
SEQ_TB_FILE_IDX.NEXTVAL
, #{ORIGINAL_FILE_NAME}
, #{STORED_FILE_NAME}
, #{FILE_SIZE}
, 'N'
, SYSDATE
, 'USER01'
, SYSDATE
, 'USER01'
)
</insert>
</mapper>
참조 : https://addio3305.tistory.com/83?category=772645
댓글