티스토리 뷰

1.    Spring MVC에서 파일 업로드에 필요한 것

-      1 ) 파일 업로드 처리용 라이브러리 설정 ( pom.xml – dependency )

-      2 ) 스프링 설정 파일에서 Multipart Request를 핸들링 할 수 있는 Bean 설정 ( Servlet-context.xml )

-      + spring-webmvc sub framework가 빈 클래스를 제공해 준다.

-      + 이를 빈으로 등록하면, BeanMultipart 처리를 위해 commons-fileupload 라이브러리를 이용한다.

 

2.    파일 업로드시 UUID를 사용하는 이유

-      1 ) 업로드된 파일을 저장할 시, 원본 파일명으로 저장해서는 안된다. (***)

-      + 불특정 다수가 파일 업로드를 수행가능하기에, 원본 파일명이 동일하게 업로드 될 가능성이 존재한다.

-      + 만약 동일원본파일명으로 파일을 다른 사람이 업로드할 경우, 기존에 업로드된 파일을 overwrite할 가능성이 있다.

-      + 그렇기에 파일을 업로드 시, 각 파일을 UUID 기반의 파일명으로 저장해야 한다.

-      2 ) 파일 업로드 정보를 저장하는 테이블에는 적어도 아래의 3가지 정보 저장해야 된다.

-      + [1] 원본 파일명 : 나중에 다운로드 처리시, 원본파일명으로 내려 받을 수 있도록 하기 위해서

-      + [2] UUID기반의 실제 저장파일의 절대경로 ( ex. C:/Temp/upload/YYYYMMDD/UUID )

-      + [3] Content-Type

 


[ 1. Spring MVC - 파일 업로드 ] (***)

 

[ 1 - 1. pom.xml 파일에서 파일 업로드 관련 dependency 추가하기 ]

 

더보기

[ + 코드 보기 ]

<?xml version="1.0" encoding="UTF-8"?>
 <!-- 1~2칸 띄우기 -->

<project
   xmlns="http://maven.apache.org/POM/4.0.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">

   <modelVersion>4.0.0</modelVersion>

   <groupId>org.zerock</groupId>
   <artifactId>chap06</artifactId>
   <version>1.0.0-BUILD-SNAPSHOT</version>

   <!-- 웹에 패키징되는 건 war -->
   <packaging>war</packaging>

   <name>chap06</name>
   <url>http://chap06.example.com</url>
   <description>Spring MVC project - maven</description>


   <!-- 프로젝트 어디서든 가져다 쓸수 있도록 정의 -->
   <properties>
      
      <!-- 자바 버전 -->
      <java-version>11</java-version>
      <!-- <java-home>${env.JAVA_HOME}</java-home> -->

      <!-- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <maven.compiler.source>11</maven.compiler.source>
      <maven.compiler.target>11</maven.compiler.target> -->
      
      <!-- 스프링 프레임 워크 버전 -->
      <org.springframework-version>5.3.21</org.springframework-version>

      <org.aspectj-version>1.9.9.1</org.aspectj-version>
      <org.apache.logging.log4j-version>2.17.2</org.apache.logging.log4j-version>
   </properties>


   <dependencies>

      <!-- =============== Logging =============== -->

      <dependency>
         <groupId>org.apache.logging.log4j</groupId>
         <artifactId>log4j-core</artifactId>
         <version>${org.apache.logging.log4j-version}</version>
      </dependency>

      <!-- For Spring framework, HikariCP, DriverSpy logging -->
      <dependency>
         <groupId>org.apache.logging.log4j</groupId>
         <artifactId>log4j-slf4j-impl</artifactId>
         <version>${org.apache.logging.log4j-version}</version>
      </dependency>


      <!-- ============= Servlet/JSP ============= -->
      <!-- 이에 따라서 선택해야 하는 톰켓 버전이 달라질 수 있다. -->
         
      <dependency>
         <groupId>javax.servlet</groupId>
         <artifactId>javax.servlet-api</artifactId>
         <version>4.0.1</version>
         <scope>provided</scope>
      </dependency>
   
      <dependency>
         <groupId>javax.servlet.jsp</groupId>
         <artifactId>javax.servlet.jsp-api</artifactId>
         <version>2.3.3</version>
         <scope>provided</scope>
      </dependency>

      <dependency>
         <groupId>javax.servlet</groupId>
         <artifactId>jstl</artifactId>
         <version>1.2</version>
      </dependency>


      <!-- =============== AspectJ (스프링할 때 rt, weaver 꼭)=============== -->
            
      <dependency>
         <groupId>org.aspectj</groupId>
         <artifactId>aspectjrt</artifactId> <!-- rt = runtime -->
         <version>${org.aspectj-version}</version>
      </dependency>

      <dependency>
         <groupId>org.aspectj</groupId>
         <artifactId>aspectjweaver</artifactId>
         <version>${org.aspectj-version}</version>
      </dependency>


      <!-- ================ Spring (밑의 2개는 최소한 꼭) =============== -->

      <!-- spring bean container -->
      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-context</artifactId>
         <version>${org.springframework-version}</version>
      </dependency>

      <!-- spring mvc 구동 -->
      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-webmvc</artifactId>
         <version>${org.springframework-version}</version>
      </dependency>

      <!-- spring test -->
      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-test</artifactId>
         <version>${org.springframework-version}</version>
         <scope>test</scope>
      </dependency>

      <!-- spring-jdbc -->
      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-jdbc</artifactId>
         <version>5.3.21</version>
      </dependency>

      <!-- spring-tx manager -->
      <!-- X/Open XA 규약대로 모든 TX Manager가 구현되어 있다. -->
      <!-- 1. WAS 2. Spring MVC 3. JDBC Driver 4. Database Instance 안에 구현체가 있다. -->
      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-tx</artifactId>
         <version>5.3.21</version>
      </dependency>

      <!-- =============== JDBC =============== -->

      <dependency>
         <groupId>org.bgee.log4jdbc-log4j2</groupId>
         <artifactId>log4jdbc-log4j2-jdbc4</artifactId>
         <version>1.16</version>
      </dependency>
        
      <dependency>
         <groupId>com.oracle.database.jdbc</groupId>
         <artifactId>ojdbc8-production</artifactId>
         <version>21.5.0.0</version>
      
         <type>pom</type>
      
         <exclusions>
            <exclusion>
               <groupId>com.oracle.database.ha</groupId>
               <artifactId>simplefan</artifactId>
            </exclusion>
      
            <exclusion>
               <groupId>com.oracle.database.ha</groupId>
               <artifactId>ons</artifactId>
            </exclusion>
      
            <exclusion>
               <groupId>com.oracle.database.jdbc</groupId>
               <artifactId>rsi</artifactId>
            </exclusion>
      
            <exclusion>
               <groupId>com.oracle.database.jdbc</groupId>
               <artifactId>ucp</artifactId>
            </exclusion>
      
            <exclusion>
               <groupId>com.oracle.database.xml</groupId>
               <artifactId>xdb</artifactId>
            </exclusion>
      
            <exclusion>
               <groupId>com.oracle.database.xml</groupId>
               <artifactId>xmlparserv2</artifactId>
            </exclusion>
         </exclusions>
      </dependency>


      <!-- =============== Testing (버전 4,5 둘다 쓸줄알아야함)=============== -->

      <dependency>
         <groupId>org.junit.jupiter</groupId>
         <artifactId>junit-jupiter-api</artifactId>
         <version>5.8.2</version>
         <scope>test</scope>
      </dependency>

      <!-- ================ DataSource ================= -->

      <!-- HikariCP -->
      <dependency>
         <groupId>com.zaxxer</groupId>
         <artifactId>HikariCP</artifactId>
         <version>5.0.1</version>

         <!-- 이 부분만 뺀다 -->
         <exclusions>
            <exclusion>
               <groupId>org.slf4j</groupId>
               <artifactId>slf4j-api</artifactId>
            </exclusion>
         </exclusions>

      </dependency>

      <!-- ================ MyBatis ================= -->
      <dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.5.10</version>
		</dependency>

      <!-- 마이바티스와 스프링 연동 라이브러리 -->
      <dependency>
         <groupId>org.mybatis</groupId>
         <artifactId>mybatis-spring</artifactId>
         <version>2.0.7</version>
      </dependency>

      <!-- ================ Data-Binding ================= -->

      <!-- Java 객체를 JSON으로 변환시켜 준다. -->
      <dependency>
         <groupId>com.fasterxml.jackson.core</groupId>
         <artifactId>jackson-databind</artifactId>
         <version>2.13.3</version>
      </dependency>

      <!-- Java 객체를 XML로 변환해 준다. -->
      <dependency>
         <groupId>com.fasterxml.jackson.dataformat</groupId>
         <artifactId>jackson-dataformat-xml</artifactId>
         <version>2.13.3</version>
      </dependency>

      <!-- ================ File - upload ================= -->

      <dependency>
         <groupId>commons-fileupload</groupId>
         <artifactId>commons-fileupload</artifactId>
         <version>1.4</version>
      </dependency>
    
      <!-- ================ Misc ================= -->

      <dependency>
         <groupId>javax.inject</groupId>
         <artifactId>javax.inject</artifactId>
         <version>1</version>
      </dependency>
   
      <!-- 롬복 -->
      <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
         <version>1.18.24</version>
         <scope>provided</scope>
      </dependency>
           
   </dependencies>


    <build>

      <finalName>/</finalName>

        <plugins>

            <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-war-plugin</artifactId>
            <version>3.3.2</version>
            </plugin>

            <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-eclipse-plugin</artifactId>
            <version>2.10</version>

                <configuration>
                    <downloadSources>true</downloadSources>
                    <downloadJavadocs>false</downloadJavadocs>
                </configuration>
            </plugin>

            <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-compiler-plugin</artifactId>
               <version>3.10.1</version>

               <configuration>
                  <release>${java-version}</release>
               </configuration>
            </plugin>

        </plugins>

    </build>

</project>

 

[ 1 - 2. servlet-context.xml 파일에서 Bean 등록 ]

 

더보기

[ + 코드 보기 ]

<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

	<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
	
	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />

	<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
	<!-- /resources/**의 의미는 resources폴더 아래에 있는 어떠한(모든) 자원을 의미한다. -->
	<!-- < resources mapping location >의 경우 파일을 생성하여 매핑할 수 있으며, 같은 location이여도 mapping을 추가할 수 있다. -->
	<!-- 설정파일을 수정하였으면, WAS를 내렸다가 올려야 한다. -->
	<!-- 아래의 태그로 인해 URL mapping처럼 http://localhost:8080/resources/파일명으로 접근이 가능하다. -->
	<resources mapping="/resources/**" location="/resources/" />

	<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
	<!-- View로 어떤 JSP가 사용되어야 하는지 알려준다. -->
	<!-- ViewResolver는 MVC 패턴에서 Model 데이터를 이용하여 최종 응답화면을 생성할 View 역할을 수행할 대상을 찾아낸다. -->

	<!-- [ 예전 버전 ] -->
	<!-- + 예전 버전은 빈으로 등록하는 방법을 사용했다. -->
	<!-- <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> -->
		<!-- beans:property는 setPrefix와 setSuffix를 value값으로 해주는것이다. -->
		<!-- <beans:property name="prefix" value="/WEB-INF/views/" /> -->
		<!-- <beans:property name="suffix" value=".jsp" /> -->
	<!-- </beans:bean> -->

	<!-- [ 현재 버전 ] -->
	<!-- View Resolver -->
	<view-resolvers>
		<jsp prefix="/WEB-INF/views/" suffix=".jsp" />
	</view-resolvers>

	<!-- 단순 View 호출을 위한 설정 -->
	<!-- path = 전체 URI 지정 -->
	<!-- view-name = 보여줄 jsp 명 -->
	<view-controller path="/sample2/getsample" view-name="sample" status-code="200" />

	<!-- 파일 업로드 -->
	<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
		<beans:property name="defaultEncoding" value="utf8" />
		<!-- 최대 10mb까지 업로드 가능 -->
		<beans:property name="maxInMemorySize" value="10485760" />
		<beans:property name="maxUploadSize" value="10485760" />
		<!-- 파일 1개당 최대 사이즈는 2mb로 제한 -->
		<beans:property name="maxUploadSizePerFile" value="2097152" />
		<!-- 파일의 이름을 저장할지 지정 -->
		<beans:property name="preserveFilename" value="true" />
		<!-- 업로드할 정소 지정 -->
		<!-- 로컬에서는 경로를 file:///로 시작해야 한다. -->
		<beans:property name="uploadTempDir" value="file:///C:/Temp/upload" />
	</beans:bean>

	<!-- base-package에 작성한 패키지 내에 있는 것을 빈으로 등록시킨다. -->
	<context:component-scan base-package="org.zerock.myapp.controller" />
	
</beans:beans>

 

[ 1 - 3. Controller - handler 생성 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.controller;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;

import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;


@Log4j2
@NoArgsConstructor

@RequestMapping("/fileupload/*") // 기본 URI
@Controller
public class FileUploadController {
	
	// ================================================
	// 1. 파일 업로드 화면 요청 핸들러 메소드
	// ================================================
	
	@GetMapping("page") // 상세 URI
	public void page() {
		
		log.trace("page() invoked.");
		// + void의 경우에는 view의 이름이 기본 + 상세 URI로 자동호출된다.
		// + 이와 같이 단순하게 view를 띄우는 것은 servlet-context.xml파일에서 지정할 수도 있으나
		// + 그렇게 처리할 경우 @RequestMapping에서 Mapping table로 관리가 불가능하기에 지양해야 한다.
		
	} // page
	
	// ================================================
	// 2. 파일 업로드 처리용 핸들러 메소드 ( 비추 )
	// ================================================
	
	@PostMapping("/notdoit")
	// + 매개변수로 들어오는 files가 비어있는지 확인
	// + assert로 nonnull을 확인하는 방법은 테스트에서만 사용하는 것이 좋다.
	public void notdoit( @NonNull List <MultipartFile> files ) {
		
		log.trace("notdoit() invoked.");
		files.forEach(log::info);
		
		// + 업로드할 타켓 폴더 지정
		String tergetDir = "C:/temp/upload/";
		
		// + MultipartFile 객체가 제공하는 정보 4가지 출력
		// + 람다식으로 처리하는 것은, 파일 저장시 예외가 발생하면,
		// + 무조건 예외를 알아서 처리해야 하며, 위로 던질 수 없기에 추천하지 않는다.
		files.forEach(f -> {
			log.info("1. ContentType : {}",f.getContentType());
			log.info("2. 전송파라미터의 Name : {}", f.getName());
			log.info("3. 원본 파일명 : {}", f.getOriginalFilename());
			log.info("4. size : {}", f.getSize());
			log.info("5. Resource : {}", f.getResource());
			log.info("==============================================");
			
			// + 업로드된 파일을 target 디렉토리에 저장
			if ( f.getSize() > 0 ) {
				
				// + C:/temp/upload/ + 원본 파일명 -> C:/temp/upload/ 밑에 파일로 저장한다는 의미
				File targetFile = new File(tergetDir + f.getOriginalFilename());
				
				try {
					f.transferTo(targetFile);
				} catch ( IllegalStateException | IOException e ) {
					e.printStackTrace();
				} // try - catch
				
			} // if : 파일 업로드 된 것만 저장
			
		}); // files.forEach
		
	} // notdoit
	
	// ================================================
	// 3. 파일 업로드 처리용 핸들러 메소드 ( 추천 ) (***)
	// ================================================
	
	@PostMapping("/doit")
	// + 매개변수로 들어오는 files가 비어있는지 확인
	public void doit( @NonNull List <MultipartFile> files, Model model ) {
		
		log.trace("doit() invoked.");
		files.forEach(log::info);
		
		// + 업로드할 타켓 폴더 지정
		String tergetDir = "C:/temp/upload/";
		
		// + MultipartFile 객체가 제공하는 정보 4가지 출력
		for ( MultipartFile f : files ) {
			log.info("1. ContentType : {}",f.getContentType());
			log.info("2. 전송파라미터의 Name : {}", f.getName());
			log.info("3. 원본 파일명 : {}", f.getOriginalFilename());
			log.info("4. size : {}", f.getSize());
			log.info("5. Resource : {}", f.getResource());
			log.info("==============================================");
			
			// + 업로드된 파일을 target 디렉토리에 저장
			if ( f.getSize() > 0 ) {
				
				// + 대용량 파일 업로드도 고려하여, 성능 보조스트림을 이용한 입/출력 처리를 해야 한다.
				// + C:/temp/upload/ + 원본 파일명 -> C:/temp/upload/ 밑에 파일로 저장한다는 의미
				String targetFile = tergetDir + f.getOriginalFilename();
				
				try {
					
					InputStream is = f.getInputStream();
					BufferedInputStream bis = new BufferedInputStream(is);
					
					FileOutputStream fos = new FileOutputStream(targetFile);
					BufferedOutputStream bos = new BufferedOutputStream(fos);
					
					// + 자원 해제
					// + @Cleanup을 통해 닫아줄 수도 있으나,
					// + @Cleanup은 닫는 순서를 지켜주지 않는다.
					try ( is; bis; fos; bos; ) {
						
						// + 바가지 생성
						byte [] buf = new byte[300];
						
						int readBytes = 0;
						while ( ( readBytes = bis.read(buf) ) != -1 ) { // EOF
							bos.write(buf, 0, readBytes);
						} // while
						
						bos.flush();
						
					} // try - with - resources
					
					// + Model 객체에 성공의 유무를 확인하는 데이터 바인딩
					model.addAttribute("__RESULT__", "SUCCESS");
					
				} catch (IOException e) {
					e.printStackTrace();
					
					// + Model 객체에 성공의 유무를 확인하는 데이터 바인딩
					model.addAttribute("__RESULT__", "partially Failed.");
				} // try - catch
				
			} // if : 파일 업로드 된 것만 저장
						
		} // enhanced for
		
	} // doit
	

} // end class

 

[ 1 - 4. View 생성 ] 

 

더보기

[ + 코드 보기 ]

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!DOCTYPE html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>View - void.jsp</title>
</head>

<body>

    <h1>/WEB-INF/views/fileupload/page.jsp</h1>
    <hr>

    <h1> I AM PAGE!!!!</h1>

    <form 
        action="/fileupload/doit" 
        method="POST" 
        enctype="multipart/form-data">

        <div>file1 : <input type="file" name="files"></div>
        <div>file2 : <input type="file" name="files"></div>
        <div>file3 : <input type="file" name="files"></div>
        <div>file4 : <input type="file" name="files"></div>
        <div>file5 : <input type="file" name="files"></div>
        <div><input type="submit" value="Upload"></div>

    </form>
    
</body>

</html>

더보기

[ + 코드 보기 ]

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!DOCTYPE html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>View - doit.jsp</title>
</head>

<body>

    <h1>/WEB-INF/views/fileupload/doit.jsp</h1>
    <hr>

    <h1> I AM DOIT!!!!</h1>

    <h3>File Upload Result : ${__RESULT__}</h3>
    
</body>

</html>

[ 2. UUID 사용 version ] (****)

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.controller;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import org.zerock.uuid.UUIDGenerator;

import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;


@Log4j2
@NoArgsConstructor

@RequestMapping("/fileupload/*") // 기본 URI
@Controller
public class FileUploadController {
	
	// ================================================
	// 1. 파일 업로드 화면 요청 핸들러 메소드
	// ================================================
	
	@GetMapping("page") // 상세 URI
	public void page() {
		
		log.trace("page() invoked.");
		// + void의 경우에는 view의 이름이 기본 + 상세 URI로 자동호출된다.
		// + 이와 같이 단순하게 view를 띄우는 것은 servlet-context.xml파일에서 지정할 수도 있으나
		// + 그렇게 처리할 경우 @RequestMapping에서 Mapping table로 관리가 불가능하기에 지양해야 한다.
		
	} // page
	
	// ================================================
	// 2. 파일 업로드 처리용 핸들러 메소드 ( 비추 )
	// ================================================
	
	@PostMapping("/notdoit")
	// + 매개변수로 들어오는 files가 비어있는지 확인
	// + assert로 nonnull을 확인하는 방법은 테스트에서만 사용하는 것이 좋다.
	public void notdoit( @NonNull List <MultipartFile> files ) {
		
		log.trace("notdoit() invoked.");
		files.forEach(log::info);
		
		// + 업로드할 타켓 폴더 지정
		String tergetDir = "C:/temp/upload/";
		
		// + MultipartFile 객체가 제공하는 정보 4가지 출력
		// + 람다식으로 처리하는 것은, 파일 저장시 예외가 발생하면,
		// + 무조건 예외를 알아서 처리해야 하며, 위로 던질 수 없기에 추천하지 않는다.
		files.forEach(f -> {
			log.info("1. ContentType : {}",f.getContentType());
			log.info("2. 전송파라미터의 Name : {}", f.getName());
			log.info("3. 원본 파일명 : {}", f.getOriginalFilename());
			log.info("4. size : {}", f.getSize());
			log.info("5. Resource : {}", f.getResource());
			log.info("==============================================");
			
			// + 업로드된 파일을 target 디렉토리에 저장
			if ( f.getSize() > 0 ) {
				
				// + C:/temp/upload/ + 원본 파일명 -> C:/temp/upload/ 밑에 파일로 저장한다는 의미
				File targetFile = new File(tergetDir + f.getOriginalFilename());
				
				try {
					f.transferTo(targetFile);
				} catch ( IllegalStateException | IOException e ) {
					e.printStackTrace();
				} // try - catch
				
			} // if : 파일 업로드 된 것만 저장
			
		}); // files.forEach
		
	} // notdoit
	
	// ================================================
	// 3. 파일 업로드 처리용 핸들러 메소드 ( 추천 ) (***)
	// ================================================
	
	@PostMapping("/doit")
	// + 매개변수로 들어오는 files가 비어있는지 확인
	public void doit( @NonNull List <MultipartFile> files, Model model ) {
		
		log.trace("doit() invoked.");
		files.forEach(log::info);
		
		// + 업로드할 타켓 폴더 지정
		String tergetDir = "C:/Temp/upload/";
		
		// ================================================
		// + UUID 사용 ver 2
		// + tergetDir 경로 밑에 현재 날짜 폴더를 만든 후, 그 밑에 업로드 파일을 저장해라
		
		// + Date 객체 생성
		Date date = new Date();
		
		// + Date 객체를 yyyymmdd로 포멧팅한다.
		// + /를 넣어서 경로 구분자를 만들어 줘야 한다.
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd/");
		String dateFile = sdf.format(date);
		
		// + tergetDir에 현재 날짜를 붙여서 파일 생성
		tergetDir += dateFile;
		File uploadDir = new File(tergetDir);
		
		// + 파일이 없으면 생성
		if ( !uploadDir.exists() ) {
			uploadDir.mkdir();
		} // if
		// ================================================
		
		// + MultipartFile 객체가 제공하는 정보 4가지 출력
		for ( MultipartFile f : files ) {
			log.info("1. ContentType : {}",f.getContentType());
			log.info("2. 전송파라미터의 Name : {}", f.getName());
			log.info("3. 원본 파일명 : {}", f.getOriginalFilename());
			log.info("4. size : {}", f.getSize());
			log.info("5. Resource : {}", f.getResource());
			log.info("==============================================");
			
			// + 업로드된 파일을 target 디렉토리에 저장
			if ( f.getSize() > 0 ) {
				
				// + 대용량 파일 업로드도 고려하여, 성능 보조스트림을 이용한 입/출력 처리를 해야 한다.
				// + C:/temp/upload/ + 원본 파일명 -> C:/temp/upload/ 밑에 파일로 저장한다는 의미
				// + 원본 파일명 사용한 ver
				// String targetFile = tergetDir + f.getOriginalFilename();
				
				try {
					
					// ================================================
					// + UUID 사용 ver
					// + C:/temp/upload/ + UUID -> C:/temp/upload/ 밑에 파일로 저장한다는 의미
					// String targetFile = tergetDir + UUIDGenerator.generateUniqueKeysWithUUIDAndMessageDigest();
					// ================================================
					// + UUID 사용 ver 2
					// + tergetDir 경로 밑에 현재 날짜 폴더를 만든 후, 그 밑에 업로드 파일을 저장해라
					
					String targetFile = tergetDir + UUIDGenerator.generateUniqueKeysWithUUIDAndMessageDigest();
					// ================================================
					
					InputStream is = f.getInputStream();
					BufferedInputStream bis = new BufferedInputStream(is);
					
					FileOutputStream fos = new FileOutputStream(targetFile);
					BufferedOutputStream bos = new BufferedOutputStream(fos);
					
					// + 자원 해제
					// + @Cleanup을 통해 닫아줄 수도 있으나,
					// + @Cleanup은 닫는 순서를 지켜주지 않는다.
					try ( is; bis; fos; bos; ) {
						
						// + 바가지 생성
						byte [] buf = new byte[300];
						
						int readBytes = 0;
						while ( ( readBytes = bis.read(buf) ) != -1 ) { // EOF
							bos.write(buf, 0, readBytes);
						} // while
						
						bos.flush();
						
					} // try - with - resources
					
					// + Model 객체에 성공의 유무를 확인하는 데이터 바인딩
					model.addAttribute("__RESULT__", "SUCCESS");
					
				} catch (IOException | NoSuchAlgorithmException e ) {
					e.printStackTrace();
					
					// + Model 객체에 성공의 유무를 확인하는 데이터 바인딩
					model.addAttribute("__RESULT__", "partially Failed.");
				} // try - catch
				
			} // if : 파일 업로드 된 것만 저장
						
		} // enhanced for
		
	} // doit
	

} // end class

 

 

728x90
댓글
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
최근에 올라온 글
Total
Today
Yesterday
공지사항