기록하는 코더

[Spring] 파일업로드 MultipartFile 본문

JAVA/spring

[Spring] 파일업로드 MultipartFile

damda_di 2023. 1. 27. 11:48

스프링에서 파일업로드


1) common-fileupload , common-io 라이브러리

2) MultipartFile (★)
<input type="file"> 일 경우 jsp에서 보낸 파일을 spring에서 MultipartFile로 받음
<input type="file" multiple> 여러개의 파일을 업로드하고 받을 수 있다. (MultipartFile[] 로 받음)

(MultipartFile로 객체의 메소드)
multipartFile.originalFileName() → 실제 파일명 구하기(★★★)
multipartFile.getSize() → 크기
multipartFile.getContentType() → 컨텐츠 타입(MIME)

 

설정해야하는 것들

1. pom.xml
2. root-context.xml
3. web.xml
4. WAS의 context.xml

 

* 단축키 Ctrl + Shift + R를 이용해서 찾으면 편하다!


1. pom.xml

```
<!-- 파일업로드 시작  -->
<!-- common-fileupload 라이브러리는 tomcat7.0버전 이후로는
	서블릿3.0이상에서 지원함
 -->
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

<!-- 파일을 처리하기 위한 의존 라이브러리 -->
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

<!-- 썸네일 -->
<!-- https://mvnrepository.com/artifact/org.imgscalr/imgscalr-lib -->
<dependency>
    <groupId>org.imgscalr</groupId>
    <artifactId>imgscalr-lib</artifactId>
    <version>4.2</version>
</dependency>

<!-- https://mvnrepository.com/artifact/net.coobird/thumbnailator -->
<dependency>
    <groupId>net.coobird</groupId>
    <artifactId>thumbnailator</artifactId>
    <version>0.4.8</version>
</dependency>
<!-- 파일업로드 끝  -->

```

 

2. root-context.xml

```
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 파일업로드 용량 (10MB)-->
    <property name="maxUploadSize" value="10485760"/>
    <property name="defaultEncoding" value="UTF-8" />
  </bean>
  
  <!-- 파일업로드 디렉토리 설정 -->
  <bean id="uploadPath" class="java.lang.String">
    <constructor-arg value="[프로젝트 내 폴더, 절대경로로 설정]"/>
  </bean>

```

 

전체코드

더보기
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
	
	<!-- Root Context: defines shared resources visible to all other web components -->
	<!-- root-context.xml : 스프링 설정 파일
	스프링 설정? view와 관련되지 않은 객체를 정의
	Service(기능), DAO(Repository : 저장소), DB 등 비즈니스 로직과 관련된 설정
	BasicDataSource dataSource = new BasicDataSource();
	dataSource.setDriverClassName("oracle.jdbc.driver.OracleDriver");
	
	 -->
	<!-- dataSource : 데이터베이스와 관련된 정보를 설정 --> 
	<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"
			destroy-method="close" >
		<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
		<property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />
		<property name="username" value="spring" />
		<property name="password" value="java" />
	</bean> 
	 
	 <!-- 데이터베이스와 연결을 맺고 끊어질 때까지의 라이프 사이클을 관리해주는 sqlSession 객체를 생성 
	 1) dataSource
	 2) 매퍼 xml의 위치를 지정 : src/main/resources
	 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" >
		<property name="dataSource" ref="dataSource" /> <!-- ref를 통해 bean 객체가 주입됨 -->
		<property name="mapperLocations" value="classpath:/sqlmap/**/*_SQL.xml" />
		<property name="configLocation" value="/WEB-INF/mybatisAlias/mybatisAlias.xml" />
	</bean>
	
	<!-- 데이터베이스에 개별적으로 쿼리를 실행시키는 객체 
		이 객체를 통해 query를 실행함
	-->
	<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg index="0" ref="sqlSessionFactory" />	
	</bean>
	
	<bean id="multipartResolver" 
		class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
		<!-- 파일업로드 용량 (10MB) -->
		<property name="maxUploadSize" value="10485760" />
		<property name="defaultEncoding" value="UTF-8" />
	</bean>
	
	<!-- 파일업로드 디렉토리 설정 -->
	<bean id="uploadPath" class="java.lang.String">
		<constructor-arg value="C:\\upload" />
	</bean>
	
	
</beans>

 

3. web.xml

<load-on-startup>1</load-on-startup>

<!-- web.xml의 설정은 WAS(Tomcat) 자체 설정일 뿐임. -->
<!-- multipart-config : 메모리사이즈, 업로드 파일 저장 위치, 최대 크기 설정 -->
<multipart-config>
	<location>c:\\upload</location><!-- 업로드 되는 파일을 저장할 공간 -->
	<max-file-size>20971520</max-file-size><!-- 업로드 파일의 최대 크기 1MB * 20 -->
	<max-request-size>41943040</max-request-size><!-- 한 번에 올릴 수 있는 최대 크기 40MB -->
	<file-size-threshold>20971520</file-size-threshold><!-- 메모리 사용 크기 20MB -->
</multipart-config>

<!-- multipart filter 추가하기 -->
<filter>
	<display-name>springMultipartFilter</display-name>
	<filter-name>springMultipartFilter</filter-name>
	<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<filter-mapping>
	<filter-name>springMultipartFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

 

전체코드

더보기
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_3_1.xsd">

	<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/spring/root-context.xml</param-value>
	</context-param>
	
	<!-- Creates the Spring Container shared by all Servlets and Filters -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- Processes application requests -->
	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
		
	<!-- web.xml의 설정은 WAS(Tomcat) 자체 설정일 뿐임 -->
	<!-- multipart-config : 메모리사이즈 업로드 파일 저장 위치, 최대 크기 설정 -->
	<multipart-config>
		<location>C:\\upload</location> <!-- 업로드 되는 파일을 저장할 공간 -->
		<max-file-size>20971520</max-file-size><!-- 업로드 파일의 최대 크기 1MB * 20 -->
		<max-request-size>41943040</max-request-size><!-- 한 번에 올릴 수 있는 최대 크기 40MB -->
		<file-size-threshold>20971520</file-size-threshold><!-- 메모리 사용 크기 20MB -->
	</multipart-config>
	</servlet>
		
	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
   <!-- multipart filter 추가하기-->
   <filter>
		<display-name>springMultipartFilter</display-name>
		<filter-name>springMultipartFilter</filter-name>   
   		<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
   </filter>
   <filter-mapping>
   	<filter-name>springMultipartFilter</filter-name>
   	<url-pattern>/*</url-pattern>
   </filter-mapping>
	
	<!-- 한글 처리 -->
   <filter>
      <filter-name>encodingFilter</filter-name>
      <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
      <init-param>
         <param-name>encoding</param-name>
         <param-value>UTF-8</param-value>
      </init-param>
      <init-param>
         <param-name>forceEncoding</param-name>
         <param-value>true</param-value>
      </init-param>
   </filter>
   <filter-mapping>
      <filter-name>encodingFilter</filter-name>
      <url-pattern>/*</url-pattern>
   </filter-mapping>
   
</web-app>

 

4. WAS의 context.xml

추가해야할 요소

<Context allowCasualMultipartParsing="true" >
	<!-- 케시문제 해결 -->
	<Resources cachingAllowed="true" cacheMaxSize="100000"></Resources>

 

전체코드

더보기
<?xml version="1.0" encoding="UTF-8"?>
<!-- The contents of this file will be loaded for each web application -->

<!-- 파싱 : 구문분석, 의미분석 -->
<Context allowCasualMultipartParsing="true">
	<!-- 캐시 문제 해결 -->
	<Resources cachingAllowed="true" cachemaxSize="100000"></Resources>

    <!-- Default set of monitored resources. If one of these changes, the    -->
    <!-- web application will be reloaded.                                   -->
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>

    <!-- Uncomment this to disable session persistence across Tomcat restarts -->
    <!--
    <Manager pathname="" />
    -->
</Context>

 


실행코드

 

Controller

package kr.or.ddit.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

import kr.or.ddit.service.LprodService;
import kr.or.ddit.vo.LprodVO;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.List;

import org.apache.commons.dbcp2.BasicDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;


/*   Controller 어노테이션
    스프링 프레임워크에게 "이 클래스는 웹 브라우저(클라이언트)의 요청(request)를
    받아들이는 컨트롤러야"라고 알려주는 것임
    스프링은 servlet-context.xml의 context:component-scan의 설정에 의해 이 클래스를 자바빈 객체로 미리 등록(메모리에 바인딩)
 */
@RequestMapping("/lprod")
@Slf4j
@Controller
public class LprodController {
	
	// 요청URL : /lprod/uploadForm
	// 요청방식 : GET
	@GetMapping("/uploadForm")
	public String uploadForm() {
		// forwarding
		return "lprod/uploadForm";
	}
	
	/*
	요청URL : /lprod/uploadFormAction
	요청파라미터 : uploadFile이라는 이름의 파일 객체
	요청방식 : POST
	*/
	@PostMapping("/uploadFormAction")
	public String uploadFormAction(MultipartFile uploadFile) {
		// MultipartFile : 스프링에서 제공해주는 타입 (인터페이스)
		/*
			String, getOriginalFileName() : 업로드 되는 파일의 실제 파일명
			boolean, isEmpty() : 파일이 없다면 true
			long, getSize() : 업로드되는 파일의 크기
			transferTo(File file) : 파일을 저장 
		*/
		// 파일이 저장되는 경로
		String uploadFolder ="C:\\upload";
		
		//make folder 시작 ---------------------------
		// c:\\upload\\2023\\01\\27
		File uploadPath = new File(uploadFolder,getFolder());
		log.info("upload Path : " + uploadPath );
		
		// 만약 연/월/일 해당 폴더가 없다면 생성
		if(uploadPath.exists() == false) {
			uploadPath.mkdirs();
		}
		//make folder 끝 ---------------------------
		
		// 파일명
		String uploadFileName = uploadFile.getOriginalFilename();
		
		log.info("--------------------------");
		log.info("이미지 명 : " + uploadFileName);
		log.info("파일 크기 : " + uploadFile.getSize());

		// ------------ 파일명 중복 방지 시작 ------------
		// java.util.UUID => 랜덤값을 생성
		UUID uuid = UUID.randomUUID();
		// SDFKOJWEFOIJEWF_.핑구.jsp
		uploadFileName = uuid.toString() + "_" + uploadFileName;
		// ------------ 파일명 중복 방지 끝 ------------
		// (어디에, 무엇을)
		// 계획을 세움
		File saveFile = new File(uploadPath,uploadFileName);
		
		try {
			// 계획 실행. 파일 복사됨(클라이언트의 파일을 서버의 공간으로 복사)
			uploadFile.transferTo(saveFile);
			
			AttachVO attachVO = new AttachVO();
			
			// 1) filename : /2023/01/27/32321424b09_nullPointer.jpg
			String filename = "/" + getFolder().replace("\\", "/") + "/" + uploadFileName;
			
			attachVO.setFilename(filename);
			// 2) filesize
			
			Long l = uploadFile.getSize();
			attachVO.setFilesize(l.intValue());
			
			// 3) thumbnail
			String thumb = "/" + getFolder().replace("\\", "/") + "/s_" + uploadFileName;
			attachVO.setThumbnail(thumb);
			
			log.info("attachVO : " + attachVO);
			
			
			// 이미지인지 체킹
			try {
				// MIME 타입을 가져옴. images/jpeg
				String contentType = Files.probeContentType(saveFile.toPath());
				log.info("contentType : " + contentType);
				// MIME 타입 정보가 images로 시작하는지 여(true) 부(false)
				if(contentType.startsWith("image")) { // 이미지가 맞다면 true
					// c:\\upload\\2023\\01\\27\s_12345467546_nullPointer.jpg
					FileOutputStream thumbnail = new FileOutputStream(
							new File(uploadPath, "s_"+uploadFileName)
						);
					// 섬네일 생성
					Thumbnailator.createThumbnail(uploadFile.getInputStream(), thumbnail,100,100);
					thumbnail.close();
				}
				int result = this.lprodService.uploadFormAction(attachVO);
				log.info("result : " + result);
					
			}catch(IOException e) {
				e.printStackTrace();
			}
			
		} catch (IllegalStateException | IOException e) {
			log.error(e.getMessage());
		}
		return "redirect:/lprod/uploadForm";
	}
	
	/*
	요청URL : /lprod/uploadFormMultiAction
	요청파라미터 : uploadFile이라는 이름의 파일 객체
	요청방식 : POST
	*/
	@PostMapping("/uploadFormMultiAction")
	public String uploadFormMultiAction(MultipartFile[] uploadFile) {
		// 파일이 저장되는 경로
		String uploadFolder ="C:\\upload";
		
		//make folder 시작 ---------------------------
		// c:\\upload\\2023\\01\\27
		File uploadPath = new File(uploadFolder,getFolder());
		log.info("upload Path : " + uploadPath );
		
		// 만약 연/월/일 해당 폴더가 없다면 생성
		if(uploadPath.exists() == false) {
			uploadPath.mkdirs();
		}
		//make folder 끝 ---------------------------
		
		for(MultipartFile multipartfile : uploadFile) {
			// 파일명
			String uploadFileName = multipartfile.getOriginalFilename();
			
			log.info("----------");
			log.info("이미지 명 : " +uploadFileName);
			log.info("파일 크기 : " + multipartfile.getSize());
			log.info("컨텐츠(MIME) 타입 : " + multipartfile.getContentType());
			
			// ------------ 파일명 중복 방지 시작 ------------
			// java.util.UUID => 랜덤값을 생성
			UUID uuid = UUID.randomUUID();
			// SDFKOJWEFOIJEWF_.핑구.jsp
			uploadFileName = uuid.toString() + "_" + uploadFileName;
			// ------------ 파일명 중복 방지 끝 ------------
			
			// (어디에, 무엇을)
			// 계획을 세움
			// c:\\upload\\2023\\01\\27\\DFOLEKFEOK_핑구.jpg
			File saveFile = new File(uploadPath,uploadFileName);
			
			try {
				// 계획 실행. 파일 복사됨(클라이언트의 파일을 서버의 공간으로 복사)
				multipartfile.transferTo(saveFile);

				
				//---------------------------------------------
				AttachVO attachVO = new AttachVO();
				
				// 1) filename : /2023/01/27/32321424b09_nullPointer.jpg
				String filename = "/" + getFolder().replace("\\", "/") + "/" + uploadFileName;
				
				attachVO.setFilename(filename);
				// 2) filesize
				
				Long l = multipartfile.getSize();
				attachVO.setFilesize(l.intValue());
				
				// 3) thumbnail
				String thumb = "/" + getFolder().replace("\\", "/") + "/s_" + uploadFileName;
				attachVO.setThumbnail(thumb);
				
				log.info("attachVO : " + attachVO);
				//---------------------------------------------
				int result = this.lprodService.uploadFormAction(attachVO);
				log.info("result : " + result);
				
				try {
					// MIME 타입을 가져옴. images/jpeg
					String contentType = Files.probeContentType(saveFile.toPath());
					log.info("contentType : " + contentType);
					// MIME 타입 정보가 images로 시작하는지 여(true) 부(false)
					if(contentType.startsWith("image")) { // 이미지가 맞다면 true
						// c:\\upload\\2023\\01\\27\s_12345467546_nullPointer.jpg
						FileOutputStream thumbnail = new FileOutputStream(
								new File(uploadPath, "s_"+uploadFileName)
							);
						// 섬네일 생성
						Thumbnailator.createThumbnail(multipartfile.getInputStream(), thumbnail,100,100);
						thumbnail.close();
					}
				}catch(IOException e) {
					e.printStackTrace();
				}
			} catch (IllegalStateException | IOException e) {
				log.error(e.getMessage());
			}
		}
		// redirect
		return "redirect:/lprod/uploadForm";
	}
	
	// 연/월/일 폴더 생성
	public static String getFolder() {
		// 2023-01-27 형식(format) 지정
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		
		// 날짜 객체 생성(java.util 패키지)
		Date date = new Date();
		// 2023-01-27 => 2023(File.separator)01(File.separator)27
		// 2023-01-27 => 2023\\01\\27
		// 윈도우에서 separator가 \\
		
		// 2023-01-27
		String str = sdf.format(date);
		
		// 단순 날짜 문자를 File 객체의 폴더 타입으로 바꾸기
		// c:\\upload\\2023\\01\\27
		return str.replace("-",File.separator);
	}
	
}

 

jsp

<%@ page language="java" contentType="text/html;charset=UTF-8"%>
 <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!-- 파일업로드
1) method는 꼭 post!
2) enctype="multipart/form-data"
3) <input type="file" name="uploadFile" ... name 속성이 꼭 있어야 함
 -->
 <!-- 
 요청URL : /lprod/uploadFormAction
 요청파라미터 : uploadFile이라는 이름의 파일 객체
 요청방식 : post
  -->
 
<form action="/lprod/uploadFormAction" method ="post" enctype="multipart/form-data">
	<input type="file" name="uploadFile" />
	<button type="submit">업로드</button>
</form>
<hr />
<form action="/lprod/uploadFormMultiAction" method ="post" enctype="multipart/form-data">
	<input type="file" name="uploadFile" multiple />
	<button type="submit">다중업로드</button>
</form>

 

 

 

파일 업로드 미리보기 예제

<script text="text/javascript" src="/resources/js/jquery-3.6.0.js"></script>
<script type="text/javascript">
$(function(){
	// 이미지 미리보기 시작~
	let sel_file = [];
	
	// 이벤트가 발생된 타겟
	// 	<input type="file" id="input_imgs" name="uploadFile" />
	$("#input_imgs").on("change",handleImgFileSelect);
	$("#input_imgs2").on("change",handleImgFileSelect);
	
	
	// 파라미터e : onchange 이벤트 객체
	function handleImgFileSelect(e){
		// 이벤트가 발생된 타겟 안에 들어있는 이미지 파일들을 가져옴
		let files = e.target.files;
		
		// files는 javascript object
		//이미지가 여러개 있을 수 있으므로 이미지들을 분리해서 배열로 만듦
		let fileArr = Array.prototype.slice.call(files);
		// => Array.prototype  배열로 만들어줌
		// => slice.call 잘라줌
		
		// 파일 오브젝트의 배열 반복. f : 배열 안에 들어있는 각각의 이미지 파일 객체
		fileArr.forEach(function(f){
			// 이미지 파일이 아닌 경우 이미지 미리보기 실패 처리(image/jpeg)
			if(!f.type.match("image.*")){
				alert("이미지 확장자만 가능합니다");
				//함수 종료
				return;
			}
			// 이미지 객체(f)를 전역 배열타입 변수(sel_file)에 넣자
			sel_file.push(f);
			
			// 이미지 객체를 읽을 자바 스크립트의 reader 객체 생성
			let reader = new FileReader();
			
			// e : reader 객체가 이미지 객체를 읽는 이벤트
			reader.onload = function(e){
				// e.target : 이미지 개체
				// e.target.result : reader가 이미지를 다 읽은 결과
				 let img_html ="<p><img src=\""+e.target.result+"\" /></p>";
				 // div 사이에 이미지가 렌더링되어 화면에 보임
				 // 객체.append : 누적
				 // 객체.html : 새로고침
				 // --> ajax쪽에서 사용
				 // innerHTML :  J/S
// 				 $('.imgs_wrap').html(img_html);
				 $('.imgs_wrap').append(img_html);
			}
			// f : 이미지 파일 객체를 읽은 후 다음 이미지 파일(f)을 위해 초기화 함
			reader.readAsDataURL(f);
			// 실제로 데이터를 읽어오는 곳
			
		});
	}
	// 이미지 미리보기 끝~
});

</script>

'JAVA > spring' 카테고리의 다른 글

[Spring] 페이징 처리  (0) 2023.02.09
[Spring] form 태그 라이브러리  (0) 2023.02.06
[Spring] 파일 다운로드  (0) 2023.02.02
[Spring] ajax 데이터 처리  (0) 2023.02.02
[Spring] Mybatis - resultMap  (0) 2023.01.31