본문 바로가기
SPRING

[SPRING] 파일업로드

by 김뚱 2019. 3. 6.

파일업로드 방법 정리 순서

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


728x90
반응형

'SPRING' 카테고리의 다른 글

[SPRING] ResponseEntity  (1) 2019.07.05
[SPRING] RestTemplate  (0) 2019.07.04
[SPRING] Task Scheduler 설정 및 사용방법  (0) 2019.07.02
[SPRING] Tomcat JNDI 설정  (0) 2019.06.16

댓글