Spring 파일 업로드

문이빈·2023년 9월 25일
0

파일 업로드

  1. 등록 폼에 파일 업로드 필드 추가
  • 컨트롤러를 MultipartFile 객체를 이용해서 업로드 가능한 컨트롤로 구현해야 한다.
  • 업로드된 파일은 대부분 바이너리 파일이다
  • 파일이 첨부된 폼을 전송하는 경우 콘텐츠형식은 multipart/form-data를 해야 한다
  • 폼이 전송되면 이미지 파일의 바이너리 데이터를 포함하는 한 부분이 멀티파트 형식으로 전달된다.
  1. Controller의 업로드된 파일을 받도록 수정

  2. 스프링에 멀티파트 파일 리졸버(multipart file resolver) 설정

  • 실제로 파일 업로드 기능이 동작하기 위해서는 반드시 사용자가 업로드한 파일 정보가
    MultipartFile 객체에 설정되어야 하며, 이를 위해서 멀티파트 리졸버 객체가 반드시 필요하다.
  1. 다중 파일 업로드 시에는 <input type="file" name="">2개 이상 있을 때는 name속성에 같은 이름을 지정해야한다
  • 파일 업로드를 위한 스프링 설정
    DispatcherServlet은 컨트롤러에 해당 데이터를 제공하기 위하여 POST 요청에서 멀티파트 데이터를 추출하는 멀티파트 리졸버가 필요하다
    스프링이 CommonsMultipartResolver만 제공한다.

파일 업로드가 정상적으로 동작하기 위해서는 내가 만든 컨트롤러뿐만 아니라 이를 위해 멀티파트 리졸버 객체를 메모리에 올리는 두 개의 객체 생성 과정이 필요한 것이다.

<bean id="multipartResolver" 
      class="org.springframework.web.multipart.commons.CommonsMultipartResolver"
      p:maxUploadSize="5000000" />

* 2개의 jar 파일 추가

commons-io-2.13.0.jar
commons-fileupload-1.5.jar

1. pom.xml

<dependency>
	<groupId>commons-io</groupId>
	<artifactId>commons-io</artifactId>
	<version>2.130</version>
</dependency>

<dependency>
	<groupId>commons-fileupload</groupId>
	<artifactId>commons-fileupload</artifactId>
	<version>1.5/version>
</dependency>

2. servlet-context.xml

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
		<property name="maxUploadSize" value="5000000"/> <!-- 5MB -->
	</bean>

<mvc:resources location="/WEB-INF/storage/" mapping="/storage/**" />

3. Folder : storage (업로드를 받으면 여기로 받을게)

WEB-INF 안에 생성

--------------------------index.jsp--------------------------
추가
<p><a href="/chapter06_Web/user/uploadForm">파일업로드</a></p>

uploadForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style type="text/css">
table {
	border-collapse: collapse;
}
th, td {
	padding: 5px;
}
</style>
</head>
<body>
<!-- 단순 submit으로 넘기기 -->
<form enctype="multipart/form-data" method="post" action="/chapter06_Web/user/upload"> <!-- 이 두가지는 꼭 약속 -->
	<table border="1">
		<tr>
			<th>상품명</th>
				<td><input type="text" name="imageName" size="35">
			</td>
		</tr>
		
		<tr>
			<td colspan="2">
				<textarea name="imageContent" rows="10" cols="50"></textarea> 
			</td>
		</tr>
		
		<tr>
			<td colspan="2">
				<input type="file" name="img"> <!-- enctype="multipart/form-data" 타입 받기 -->
			</td>
		</tr>
		
		<tr>
			<td colspan="2" align="center">
				<input type="submit" value="이미지 업로드">
				<input type="reset" value="취소">
			</td>
		</tr>
	</table>
</form>

<!-- AJax로 넘기기 -->
</body>
</html>

4. Class : UserControllerUpload.java

package user.controller;

import java.io.File;
import java.io.IOException;

import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import user.bean.UserImageDTO;

@Controller
@RequestMapping(value="user")
public class UserControllerUpload {
	
	@GetMapping(value = "uploadForm")
	public String uploadForm() {
		return "/user/uploadForm";
	}
	
	@PostMapping(value = "upload")
	public String upload(@ModelAttribute UserImageDTO userImageDTO,
						 @RequestParam MultipartFile img,
						 HttpSession session) {
		
		// 가상 폴더(/user/storage)
		/*String filePath_lier="D:\\Spring\\workspace\\chapter06_Web\\src\\main\\webapp\\WEB-INF\\storage" 아래와 같은거임 / 대신 \\*/
		String filePath_lier="D:/Spring/workspace/chapter06_Web/src/main/webapp/WEB-INF/storage";
		
		// 실제 폴더
		String filePath = session.getServletContext().getRealPath("/WEB-INF/storage");
		System.out.println("실제 폴더 =" + filePath);
		
		String fileName = img.getOriginalFilename();
		
		// 파일 생성
		File file = new File(filePath, fileName);
		
		// 파일 이동 img.transferTo(file);->try catch
		try {
			img.transferTo(file);
		} catch (IllegalStateException e) {

			e.printStackTrace();
		} catch (IOException e) {

			e.printStackTrace();
		} 
		
		return "<img src='/chapter06_Web/storage/" + fileName + "' width='300' height='300' />";
		
	}

}

File file = new File(filePath, fileName); 파일을 올린 것은 실제 폴더에 업로드, 대신 가상폴더에는 올라가지 않음

File file = new File(filePath_lier, fileName); 위와 같이 파일을 올리면 실제 폴더와, 가상폴더 둘 다에 올라감

------------user.bean.UserImageDTO------------

package user.bean;

import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
public class UserImageDTO {

	private int seq;
	private String imageName;
	private String imageContent;
	private String image1;
	
}

다중 업로드 할때는 name 속성의 이름이 같아야 한다.

------------uploadForm.jsp------------

<tr>
	<td colspan="2">
		<textarea name="imageContent" rows="10" cols="50"></textarea> 
	</td>
</tr>

<tr>
	<td colspan="2">
		<input type="file" name="img"> <!-- enctype="multipart/form-data" 타입 받기 -->
	</td>
</tr>

------------Class : UserControllerUpload.java------------

	// ----------------- name="img"가 2개 일때 -----------------
		@PostMapping(value = "upload", produces="text/html; charset=UTF-8")
		@ResponseBody // viewResolver 타지마
		public String upload(@ModelAttribute UserImageDTO userImageDTO,
							 @RequestParam MultipartFile[] img,
							 HttpSession session) {
			
			// 가상 폴더(/user/storage)
			String filePath_lier="D:/Spring/workspace/chapter06_Web/src/main/webapp/WEB-INF/storage";
			
			// 실제 폴더
			String filePath = session.getServletContext().getRealPath("/WEB-INF/storage");
			System.out.println("실제 폴더 =" + filePath);
			
			String fileName;
			File file;
			String result ="";
			
			if(img[0] != null) {
				fileName = img[0].getOriginalFilename();
				file = new File(filePath, fileName);
				
				// img[0].transferTo(file); try-catch문
				try {
					img[0].transferTo(file);
				} catch (IOException e) {
					e.printStackTrace();
				}
				
				result = "<span><img src='/chapter06_Web/storage/" + fileName + "' width='300' height='300'/></span>";
						
			}
			
			if(img[1] != null) {
				fileName = img[1].getOriginalFilename();
				file = new File(filePath, fileName);
				
				// img[1].transferTo(file); try-catch문
				try {
					img[1].transferTo(file);
				} catch (IOException e) {
					e.printStackTrace();
				}
				
				result += "<span><img src='/chapter06_Web/storage/" + fileName + "' width='300' height='300'/></span>";
				
			}
			return result;
		}
}

한번에 여러개의 파일 선택

------------uploadForm.jsp------------

<!-- 한번에 여러개의 파일 선택 -->
<tr>
	<td colspan="2">
		<input type="file" name="img[]" multiple="multiple">
	</td>
</tr> 

------------Class : UserControllerUpload.java------------

// ----------------- 한번에 여러개의 파일을 선택 -----------------
	@PostMapping(value = "upload", produces="text/html; charset=UTF-8")
	@ResponseBody // viewResolver 타지마 
	public String upload(@ModelAttribute UserImageDTO userImageDTO,
						 @RequestParam("img[]") List<MultipartFile> list, 
						 HttpSession session) {
		
		// 실제 폴더
		String filePath = session.getServletContext().getRealPath("/WEB-INF/storage");
		System.out.println("실제 폴더 =" + filePath);
		
		String fileName;
		File file;
		String result ="";
		
		for(MultipartFile img : list) {
			fileName = img.getOriginalFilename();
			file = new File(filePath, fileName);
		
			try {
				img.transferTo(file);
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			result += "<span><img src='/chapter06_Web/storage/" + fileName + "' width='300' height='300'/></span>";
			
		}//for
		
		return result;
	}

}

파일명만 모아서 DB로 보내기

------------Class : UserControllerUpload.java------------

@Controller
@RequestMapping(value = "user")
public class UserControllerUpload {
	
    @Autowired
	private UserServiceUpload userServiceUpload;

	@GetMapping(value = "uploadForm")
	public String uploadForm() {
		return "/user/uploadForm";
	}
	// ----------------- 한번에 여러개의 파일을 선택 -----------------
	@PostMapping(value = "upload", produces="text/html; charset=UTF-8")
	@ResponseBody // viewResolver 타지마 
	public String upload(@ModelAttribute UserImageDTO userImageDTO,
						 @RequestParam("img[]") List<MultipartFile> list, 
						 HttpSession session) {
		
		// 실제 폴더
		String filePath = session.getServletContext().getRealPath("/WEB-INF/storage");
		System.out.println("실제 폴더 =" + filePath);
		
		String fileName;
		File file;
		String result ="";
		
		// 파일명만 모아서 DB로 보내기
		List<String> fileNameList = new ArrayList<String>();
		
		for(MultipartFile img : list) {
			fileName = img.getOriginalFilename();
			file = new File(filePath, fileName);
			
			fileNameList.add(fileName);
		
			try {
				img.transferTo(file);
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			result += "<span><img src='/chapter06_Web/storage/" + fileName + "' width='300' height='300'/></span>";
			
		}//for
		
		// DB
		userServiceUpload.upload(userImageDTO, fileNameList);
		
		return result;
	}

}

------------UserServiceUpload.interface 생성------------

package user.service;

import java.util.List;

import user.bean.UserImageDTO;

public interface UserServiceUpload {

	public void upload(UserImageDTO userImageDTO, List<String> fileNameList);

}

------------UserServiceUploadImpl.java 생성------------

package user.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import user.bean.UserImageDTO;
import user.dao.UserDAOUpload;

@Service
public class UserServiceUploadImpl implements UserServiceUpload {
	
	@Autowired
	private UserDAOUpload userDAOUpload;

	@Override
	public void upload(UserImageDTO userImageDTO, List<String> fileNameList) {
		userDAOUpload.upload(userImageDTO, fileNameList);
		
	}

}

------------UserDAOUpload.interface 생성------------

package user.dao;

import java.util.List;

import user.bean.UserImageDTO;

public interface UserDAOUpload {

	public void upload(UserImageDTO userImageDTO, List<String> fileNameList);

}

------------UserDAOUploadMybatis.java 생성------------

package user.dao;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import user.bean.UserImageDTO;

@Repository
@Transactional
public class UserDAOUploadMybatis implements UserDAOUpload{
	
	@Autowired
	private SqlSession sqlSession;

	@Override
	public void upload(UserImageDTO userImageDTO, List<String> fileNameList) {
		for(String fileName : fileNameList) {
			userImageDTO.setImage1(fileName);
			sqlSession.insert("userUploadSQL.upload",userImageDTO);
			
		}//for
		
	}

}

------------userUploadMapper.xml 생성------------

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="userUploadSQL">
	<insert id="upload" parameterType="user.bean.UserImageDTO">
		insert into userimage values(seq_userimage.nextval, #{imageName}, #{imageContent}, #{image1})
	</insert>
</mapper>

------------SpringConfiguration.java 수정------------

@Autowired
private ApplicationContext applicationContext; 
    
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
	SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
	sqlSessionFactoryBean.setDataSource(dataSource());
	sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("spring/mybatis-config.xml"));
	//sqlSessionFactoryBean.setMapperLocations(new ClassPathResource("user/dao/userMapper.xml"));
		
	/*
	sqlSessionFactoryBean.setMapperLocations(
			new ClassPathResource("user/dao/userMapper.xml"),
			new ClassPathResource("user/dao/userUploadMapper.xml"));
	*/
	sqlSessionFactoryBean.setMapperLocations(applicationContext.getResources("classpath:*/dao/*Mapper.xml"));
	return sqlSessionFactoryBean.getObject();
	}

------------user.sql------------

# Oracle
create table usertable(
name varchar2(30) not null,
id varchar2(30) primary key,
pwd varchar2(30) not null);

# MySQL
create table usertable(
name varchar(30) not null,
id varchar(30) primary key,
pwd varchar(30) not null);

------------userUpload.sql------------

# Oracle
create table USERIMAGE(
seq number primary key,
IMAGENAME varchar2(50) not null,
IMAGECONTENT varchar2(4000),
IMAGE1 varchar2(200));

create sequence SEQ_USERIMAGE nocycle nocache;

# MySQL
create table USERIMAGE(
seq int(10) primary key auto_increment,
IMAGENAME varchar(50) not null,
IMAGECONTENT varchar(4000),
IMAGE1 varchar(200));

파일 2개 등록

AJax 로 실행

------------index.jsp------------

<p><a href="/chapter06_Web/user/uploadForm_AJax">파일업로드_AJax</a></p>

------------UserControllerUploadAJax------------

package user.controller;

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

import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import user.bean.UserImageDTO;
import user.service.UserServiceUpload;

@Controller
@RequestMapping(value = "user")
public class UserControllerUploadAJax {
	
	@Autowired
	private UserServiceUpload userServiceUpload;
	
	@GetMapping(value = "uploadForm_AJax")
	public String uploadForm_AJax() {
		return "/user/uploadForm_AJax";
	}

	// ----------------- 한번에 여러개의 파일을 선택 -----------------
		@PostMapping(value = "upload_AJax", produces="text/html; charset=UTF-8")
		@ResponseBody // viewResolver 타지마 
		public String upload(@ModelAttribute UserImageDTO userImageDTO,
							 @RequestParam("img[]") List<MultipartFile> list, 
							 HttpSession session) {
			
			// 실제 폴더
			String filePath = session.getServletContext().getRealPath("/WEB-INF/storage");
			System.out.println("실제 폴더 =" + filePath);
			
			String fileName;
			File file;
			String result ="";
			
			// 파일명만 모아서 DB로 보내기
			List<String> fileNameList = new ArrayList<String>();
			
			for(MultipartFile img : list) {
				fileName = img.getOriginalFilename();
				file = new File(filePath, fileName);
				
				fileNameList.add(fileName);
			
				try {
					img.transferTo(file);
				} catch (IOException e) {
					e.printStackTrace();
				}
				
				result += "<span><img src='/chapter06_Web/storage/" + fileName + "' width='300' height='300'/></span>";
				
			}//for
			
			// DB
			userServiceUpload.upload(userImageDTO, fileNameList);
			
			return result;
		}
}

------------uploadForm_AJax------------

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style type="text/css">
table {
	border-collapse: collapse;
}
th, td {
	padding: 5px;
}
</style>
</head>
<body>
<form id="uploadForm"><!-- AJax 이용할 거라서 action 뺐음 $.ajax ~다 써줄거라서 enctype="multipart/form-data" method="post"뺌~-->
	<table border="1">
		<tr>
			<th>상품명</th>
				<td><input type="text" name="imageName" size="35">
			</td>
		</tr>
		
		<tr>
			<td colspan="2">
				<textarea name="imageContent" rows="10" cols="50"></textarea> 
			</td>
		</tr>
		
		<!-- 한번에 여러개의 파일 선택 -->
		<tr>
			<td colspan="2">
			<span id="showImg"></span>
			
				<img id="camera" alt="카메라" src="../image/camera.png" width="50" height="50">
				<input type="file" name="img[]" id="img" multiple="multiple" style="visibility: hidden;">
			</td>
		</tr> 
		
		<tr>
			<td colspan="2" align="center">
				<input type="button" value="이미지 업로드" id="uploadBtn">
				<input type="reset" value="취소">
			</td>
		</tr>
	</table>
</form>
<script src="http://code.jquery.com/jquery-3.7.0.min.js"></script>
<script type="text/javascript" src="../js/upload_AJax.js"></script>
<script type="text/javascript">
$('#camera').click(function() {
	// 강제 이벤트 발생 - trigger
	$('#img').trigger('click'); 
})
</script>
</body>
</html>

------------upload_AJax.js------------

$(function(){
	$('#uploadBtn').click(function(){
		var formData = new FormData($('#uploadForm')[0]);
	
		$.ajax({
			type: 'post',
			url: '/chapter06_Web/user/upload_AJax',
			enctype: 'multipart/form-data',
			processData: false,
			contentType: false,
			data: formData,
			success: function(data){
				alert(data);
				$('#showImg').html(data);
			},
			error: function(e){
				console.log(e);
			}
			
		});	
	});
});

processData

  • 기본값은 true
  • 기본적으로 Query String으로 변환해서 보내진다('변수=값&변수=값')
  • 파일 전송시에는 반드시 false로 해야 한다.(formData를 문자열로 변환하지 않는다)

contentType

  • 기본적으로 "application/x-www-form-urlencoded; charset=UTF-8"
  • 파일 전송시에는 'multipart/form-data'로 전송이 될 수 있도록 false로 설정한다

카메라 사진 눌렀을때 파일 선택과 같은 효과 발생


------------uploadForm_AJax------------
</table>
	<br>
	<div id="resultDiv"></div>

------------upload_AJax.js------------

success: function(data){
				alert(data);
				$('#resultDiv').html(data);
			},


업로드 버튼을 누르기 전에 선택한 이미지가 맞는지 확인하기 위해서 이미지 보여주기

------------uploadForm_AJax------------추가
<!-- 한번에 여러개의 파일 선택 -->
		<tr>
			<td colspan="2">
			
			<!-- 업로드 버튼을 누르기 전에 선택한 이미지가 맞는지 확인하기 위해서 이미지를 보여준다. -->
			<img id="showImg" width="70" height="70">
			
				<img id="camera" alt="카메라" src="../image/camera.png" width="50" height="50">
				<input type="file" name="img[]" id="img" multiple="multiple" style="visibility: hidden;">
			</td>
		</tr> 

<script type="text/javascript">
$('#camera').click(function() {
	// 강제 이벤트 발생 - trigger
	$('#img').trigger('click'); 
});

<!-- 업로드 버튼을 누르기 전에 선택한 이미지가 맞는지 확인하기 위해서 이미지를 보여준다. -->
$('#img').change(function() {
	readURL(this);
});

function readURL(input){
	var reader = new FileReader();
	
	reader.onload = function(e){
		$('#showImg').attr('src', e.target.result); // e.target - 이벤트가 발생하는 요소를 반환해준다.
	}
	
	reader.readAsDataURL(input.files[0]);
}
</script>

FileReader 란?

FileReader는 type이 file인 input 태그 또는 API 요청과 같은 인터페이스를 통해 File 또는 Blob 객체를 편리하게 처리할수있는 방법을 제공하는 객체이며 abort, load, error와 같은 이벤트에서 발생한 프로세스를 처리하는데 주로 사용되며, File 또는 Blob 객체를 읽어서 result 속성에 저장한다.

FileReader도 비동기로 동작한다.

FileReader.onload()
load 이벤트의 핸들러. 이 이벤트는 읽기 동작이 성공적으로 완료되었을 때마다 발생한다.

0개의 댓글