본문 바로가기
Web Development/Spring

[Spring] MultipartFille 을 활용한 다중 파일 업로드 및 출력

by graycode 2022. 6. 30.

다중 파일 업로드를 위해 HTTP 에서 제공하는 multipart/form-data 전송 방식을 사용하면

form 태그에 담긴 각각 다른 형식의 데이터들을 구분해주므로

key / value 형태의 문자 데이터와 바이너리 형태의 파일 데이터를 함께 전송할 수 있다.

 

이렇게 전송한 데이터는 Spring 의 MultipartFille 인터페이스를 통해 받을 수 있으며

이러한 기능을 구현하기 위해서 우선 몇 가지 환경 설정이 필요하다.

 

• 환경 설정 및 Form 태그 구성

SpringBoot / Maven 기준, 파일 업로드와 io 관련 디펜던시를 추가한다.

<!-- file upload -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>

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

 

application.properties 파일에 아래의 내용을 추가한다.

# file upload
spring.servlet.multipart.enabled:true
spring.servlet.multipart.max-request-size:10MB
spring.servlet.multipart.max-file-size:20MB

max-request-size 은 request 요청의 크기 제한을 설정, max-file-size 은 업로드할 파일의 크기 제한을 설정한다.

 

jsp 페이지에 작성할 간략한 form 태그 양식은 아래와 같다.

<form enctype="multipart/form-data" action="upload" method="post">
  <input type="text" name="text">
  <input type="file" name="uploadFiles" multiple>
  <button type="submit">Submit</button>
</form>

form 태그에 enctype="multipart/form-data" 속성을 추가한다.

다중 파일을 전송할 input 태그엔 둘 이상의 값을 입력할 수 있음을 명시하는 multiple 속성을 추가하며

각 태그마다 controller 에서 해당 태그를 인식할 수 있는 name 값을 명시한다.

 

• Controller 구성

public String upload(MultipartHttpServletRequest mtr, HttpServletRequest request, Model model, MultiDTO multi)
        throws Exception {

    List<MultipartFile> fileList = mtr.getFiles("uploadFiles");
    String path = request.getSession().getServletContext().getRealPath("upload");

MultipartHttpServletRequest 객체를 통해 전송받은 다중 파일들을 제네릭이 MultipartFile 인 List 에 담는다.

HttpServletRequest 객체로 파일이 저장될 미리 생성해 놓은 upload 폴더의 경로를 가져와 변수에 담는다.

 

StringBuilder files = new StringBuilder();
for (MultipartFile mf : fileList) {
    String originalFilename = mf.getOriginalFilename();
    StringTokenizer st = new StringTokenizer(originalFilename, ".");

    String[] file = new String[2];
    file[0] = st.nextToken();
    file[1] = st.nextToken();

db에 저장될 다중 파일명을 만들기 위해 StringBuilder 객체를 생성한다.

파일들을 foreach 문으로 하나씩 가져와 변수에 저장하고

"." 을 기준으로 파일명과 확장자로 분리하여 배열에 저장한다.

 

int flag = 0;
int size = (int) mf.getSize();
if (size > 10000000) {
    model.addAttribute("flag", flag);

    return "alert/upload_alert";

} else if (!file[1].equals("jpg") && !file[1].equals("gif") && !file[1].equals("jpeg")
        && !file[1].equals("png")) {
    flag = 1;
    model.addAttribute("flag", flag);

    return "alert/upload_alert";
}

파일 크기가 지정한 크기를 초과할 경우, 또는 지원하지 않는 확장자일 경우 각각 분기를 주며

아래의 내용과 같은 alert 창을 띄워 파일 업로드를 취소시킨다.

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

<c:if test="${flag == 0 }">
	<script type="text/javascript">
		alert("Files can be uploaded up to 10MB.");
		history.go(-1);
	</script>
</c:if>

<c:if test="${flag == 1 }">
	<script type="text/javascript">
		alert("Only jpg, gif, png, and jpeg files can be uploaded.");
		history.go(-1);
	</script>
</c:if>

넘겨받은 flag 변수의 값에 따라 jstl if 문을 통해 분기를 주어 alert 창을 띄우고 원래 페이지로 돌아간다.

 

String randomFilename = System.currentTimeMillis() + originalFilename;
files.append(randomFilename + ",");

mf.transferTo(new File(path + "/" + randomFilename));

저장된 파일이 여러 개이므로 System.currentTimeMillis() 로 파일명의 중복을 방지한다.

또는 UUID.randomUUID().toString() 으로 랜덤 파일명을 생성할 수 도 있다.

 

이전에 생성한 StringBuilder 객체에 새로 생성된 파일명과 "," 를 더해주고,

MultipartFile 객체를 통해 미리 변수에 저장한 경로에 해당 파일을 저장한다.

 

multi.setPhoto(files.toString());

service.insert(multi);

새로 생성된 다중 파일명을 dto에 따로 저장하고

해당 dto 를 파라미터로 전달해 db에 insert 한다.

 

예시로 파일이 3개이고 파일명이 Filename1, Filename2, Filename3 일 때

"1656009274865Filename.jpg,1656009274867Filename.jpg,1656009274868Filename.jpg," 로 저장된다.

 

전체 controller 코드는 아래와 같다.

@RequestMapping("upload")
public String upload(MultipartHttpServletRequest mtr, HttpServletRequest request, Model model, MultiDTO multi)
        throws Exception {

    List<MultipartFile> fileList = mtr.getFiles("uploadFiles");
    String path = request.getSession().getServletContext().getRealPath("upload");

    StringBuilder files = new StringBuilder();
    for (MultipartFile mf : fileList) {
        String originalFilename = mf.getOriginalFilename();
        StringTokenizer st = new StringTokenizer(originalFilename, ".");

        String[] file= new String[2];
        file[0] = st.nextToken();
        file[1] = st.nextToken();

        int flag = 0;
        int size = (int) mf.getSize();
        if (size > 10000000) {
            model.addAttribute("flag", flag);

            return "alert/upload_alert";

        } else if (!file[1].equals("jpg") && !file[1].equals("gif") && !file[1].equals("jpeg")
                && !file[1].equals("png")) {
            flag = 1;
            model.addAttribute("flag", flag);

            return "alert/upload_alert";
        }
        String randomFilename = System.currentTimeMillis() + originalFilename;
        files.append(randomFilename + ",");

        mf.transferTo(new File(path + "/" + randomFilename));
    }
    multi.setPhoto(files.toString());

    service.insert(multi);

    return "redirect:/list";
}

 

다중 이미지를 view 단에 출력 시에는 forTokens jstl 태그를 활용한다.

<c:forTokens var="image" items="${photo}" delims="," begin="0" end="4">
	<div><img src="upload/${image}"/></div>
</c:forTokens>

지정한 var 변수에 "," 을 기준으로 items 에서 파일을 하나씩 가져오고, 0 ~ 4 의 범위만큼 출력한다.

댓글