[Spring] MultipartFile 파일 업로드
1. encType
enctype은 인코딩 타입으로, 폼 데이터가 서버로 제출될 때 사용자가 입력한 값을 전송하기 좋은 형태로 변환한 다음 서버로 요청하는 타입이다. enctype은 기본값으로 application/x-www-form-urlencoded
형식이기 때문에, name=홍길동&id=hong
와 같은 key=value 형태로 데이터가 전송된다. 하지만 첨부파일이 업로드 될 때에는 <form> 태그의 메소드를 post로, enctype은 multipart/form-data로 반드시 정의해 주어야 한다. input type은 file로 설정한다.
<form method="post" action="insert.do" enctype="multipart/form-data">
<div>
<label>책이름</label>
<input type="text" name="upfiles"/>
</div>
<div>
<label>사진</label>
<input type="file" name="upfiles"/>
</div>
</form>
2. MultipartResolver
enytype이 multipart/form-data인 요청이 서버로 전달되면, 핸들러 어뎁터가 요청을 실행한다. 요청이 오면 멀티파트 요청인지 아닌지를 먼저 확인을 하고 요청 객체에 담는데, 파일 요청은 바로 전달할 수 없기 때문에 MultipartResolver에게 그 역할을 위임한다. MultipartResolver는 HttpServletRequest 객체를 MultipartHttpServletRequest로 변경한다. MultipartResolver는 context-web.xml에서 설정해 준다.
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"></property>
</bean>
3. MultipartFile
스프링에서 제공하는 MultipartFile 객체는 업로드된 첨부파일의 정보를 제공하는 객체로, 첨부파일 선택필드 하나 당 MultiFile 객체가 하나씩 생성된다. 폼의 첨부파일 선택 필드에서 첨부파일을 선택하지 않아도 해당 필드에 대한 MultipartFile객체는 생성된다. 첨부파일의 유무, 파일명, 파일 사이즈, 파일의 컨텐츠 타입, 파일 데이터, 파일과 연결된 스트림을 제공한다.
- MultipartFile의 주요 API
- boolean isEmpty()
- MultipartFile 객체에 첨부파일이 포함되어 있지 않으면 true를 반환한다.
- String getOriginalFileName()
- 업로드된 첨부파일의 파일명을 반환한다.
- String getContentType()
- 업로드된 첨부파일의 컨텐츠 타입을 반환한다.(text/plain, text/html, img/png, …)
- long getSize()
- 업로드된 첨부파일의 파일 사이즈를 반환한다.
- InputStream getInputStream()
- 업로드된 첨부파일은 temp 폴더에 임시파일로 저장되는데, 그 임시파일의 내용을 읽어오는 스트림을 반환한다.
- byte[] getBytes()
- 업로드된 첨부파일의 실제 데이터를 byte 배열에 담아서 반환한다.
- boolean isEmpty()
4. Controller 코드
@PostMapping("/insert.do")
public String save(BookInsertForm form) throws IOException {
String saveDirectory = "D:\\spring-workspace\\spring-mybatis\\src\\main\\webapp\\resources\\images";
logger.debug("입력폼 정보: " + form);
List<BookPicture> bookPictures = new ArrayList<>();
List<MultipartFile> upfiles = form.getUpfiles();
for (MultipartFile multipartFile : upfiles) {
if (!multipartFile.isEmpty()) {
// Multipartfile 객체에서 업로드된 첨부파일의 이름을 조회한다.
String filename = multipartFile.getOriginalFilename();
BookPicture bookPicture = new BookPicture();
bookPicture.setPicture(filename);
bookPictures.add(bookPicture);
// MultipartFile 객체는 임시 디렉토리에 임시파일 상태로 저장된 첨부파일을 읽어오는 스트림을 제공한다.
InputStream in = multipartFile.getInputStream();
// 지정된 폴더에 첨부파일명으로 파일을 출력하는 스트림 생성하기
FileOutputStream out = new FileOutputStream(new File(saveDirectory, filename));
// spring에서 제공하는 FileCopyUtils를 사용해서
// temp 폴더에 임시파일로 저장되어 있는 첨부파일을 읽어서 src/main/resources/images 폴더로 복사한다.
FileCopyUtils.copy(in, out);
}
}
Book book = new Book();
// BookInsertForm 객체의 멤버변수에 저장된 값을 Book 객체의 멤버변수에 복사한다.
// 멤버변수의 타입과 이름이 일치하는 값이 복사되며, 이름은 같은데 타입이 서로 다르면 예외가 발생한다.
BeanUtils.copyProperties(form, book);
// 책 정보와 책 사진 정보를 서비스 메소드에 전달해서 저장시킨다.
bookService.addNewBook(book, bookPictures);
return "redirect:list.do";
}
전체적인 순서
- commons-fileupload 라이브러리를 pom.xml에 추가
- context-web.xml 스프링 빈 설정파일에 MultipartResolver 빈 등록하기
- 첨부파일을 업로드하는 <form /> 태그에 설정 추가하기
<form method="POST" enctype="multipart/form-data" >
- 첨부파일 업로드 필드를 <form /> 태그안에 추가하기
<input type="file" name="bigImage" /> <input type="file" name="smallImages" /> <input type="file" name="smallImages" />
- 폼 입력값을 저장하는 객체에 MultipartFile 타입의 변수를 정의하기
public class BookInsertForm { private String title; private String author; private MultipartFile bigImage; private List<MultipartFile> smallImages; // getter/setter, constructor 생략 }
- src/main/webapp 폴더 밑에 /resources/images 폴더 생성하기
- BookController에서 첨부파일의 파일명 획득하고, 첨부파일을 src/main/resources/images 폴더에 저장하기
- 첨부파일 이름을 데이터베이스에 저장시키기