Spring Framework/Spring Boot

[Spring Boot] 7. File Upload

hyomee2 2024. 9. 11. 10:26

* 디렉토리 구조 및 공통 클래스

package org.example.fileupload;

@SpringBootApplication
public class Chap06FileUploadApplication {

	public static void main(String[] args) {
		SpringApplication.run(Chap06FileUploadApplication.class, args);
	}

}
package org.example.fileupload;

@Controller
public class MainController {

    @GetMapping(value = {"/", "/main"})
    public String main() {
        return "main";
    }
}

 

Single File Upload

1. application.yml 설정하기

spring-boot-starter-web에는

파일 업로드를 위한 multipartResolver가 기본 빈으로 등록되어 있기 때문에 추가적으로 등록할 필요는 없다.

파일 저장 경로, 용량 등에 대한 설정 application.yml, 또는 application.properties를 통해 할 수 있다.

 

파일의 크기가 file-size-threshold 값 이하라면

임시파일을 생성하지 않고 메모리에서 즉시 파일을 읽어서 생성할 수 있어 속도는 빠르지만

스레드가 작업을 수행하는 동안 부담이 될 수 있다.

파일 크기가  file-size-threshold 값을 초과한다면

파일은 spring.servlet.multipart.location 경로에 저장되어 해당 파일을 읽어 작업을 해야한다.

# application.yml

spring:
  servlet:
    multipart:
      # 파일 저장 경로
      location: F:/bootcamp/spring/05_spring/02_spring-web/chap06-file-upload
      # 최대 업로드 파일 크기
      max-file-size: 10MB
      # 최대 요청 파일 크기
      max-request-size: 10MB
# application.properties

# 파일 저장 경로
# 이 예제에서는 프로젝트 내부 경로에 업로드 파일을 저장할 것이므로 절대 경로를 이용한다.
# spring.servlet.multipart.location=프로젝트절대경로
spring.servlet.multipart.location=C:/Lecture/05_spring/02_spring-web/chap06-file-upload

# 최대 업로드 파일 크기
spring.servlet.multipart.max-file-size=10MB

# 최대 요청 파일 크기
spring.servlet.multipart.max-request-size=10MB

 

2. 파일 업로드 뷰 페이지 작성하기

업로드 폼을 보여줄 view 페이지를 작성한다.

<!--/main/resources /templates/main.html-->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>main</title>
</head>
<body>
    <h1>File Upload Test</h1>
    <!--
        form 태그의 기본 전송 content type은 application/x-www-form-urlencoded 으로 설정 되어 있으나
        file을 전송할 경우에는 encoding type을 multipart/form-data로 변경해야 한다.
    -->
    <h3>single file upload</h3>
    <form action="single-file" method="post" enctype="multipart/form-data">
        파일 : <input type="file" name="singleFile"><br>
        파일 설명 : <input type="text" name="singleFileDescription"><br>
        <input type="submit">
    </form>
</body>
</html>

 

3. 파일 업로드를 처리할 controller 생성하기

Spring Framework에서는 MultipartFile 클래스를 이용하여 파일 업로드를 처리한다.

이를 처리할 controller인 FileUploadController를 생성하자.

 

@RequestParam 어노테이션을 이용하여

요청 파라미터 중 singleFile이라는 이름으로 전송된 파일을 MultipartFile 객체로 받아온다.

이후 해당 파일을 rename 처리하여 저장하고, result라는 view 페이지를 반환한다.

package org.example.fileupload;

@Controller
public class FileUploadController {

    @PostMapping("/single-file")
    public String singleFileUpload(
            @RequestParam String singleFileDescription,
            @RequestParam MultipartFile singleFile,
            Model model
    ) {
        System.out.println("singleFileDescription: " + singleFileDescription);
        System.out.println("singleFile: " + singleFile);

        /* 서버로 전송 된 MultipartFile 객체(파일)를 저장할 경로 설정 */
        String root = "src/main/resources/static";
        String filePath = root + "/uploadFiles";
        
        File dir = new File(filePath);

        if(!dir.exists()) dir.mkdirs();

        /* 파일명이 중복 되면 해당 경로에서 덮어쓰기 될 수 있으므로 중복 되지 않는 이름으로 변경 */
        String originFileName = singleFile.getOriginalFilename();
        String ext = originFileName.substring(originFileName.lastIndexOf("."));
        String savedName = UUID.randomUUID() + ext;

        try {
            /* 파일 저장 */
            singleFile.transferTo(new File(filePath + "/" + savedName));
            model.addAttribute("message", "파일 업로드 완료");
        } catch (IOException e) {
            model.addAttribute("message", "파일 업로드 실패");
            throw new RuntimeException(e);
        }

        return "result";
    }
}

 

4. 반환 view 페이지 작성하기 

result.html 파일을 작성하자.

<!--/main/resources/templates/result.html-->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>result</title>
</head>
<body>
    <h1 th:text="${message}"></h1>
</body>
</html>

MultiFile Upload

1. 파일 업로드 view 페이지 작성하기

업로드 폼을 보여줄 view 페이지를 작성하자.

위에서 작성한 main.html에 코드를 추가해주었다.

<!--/main/resources/templates/main.html-->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>main</title>
</head>
<body>

    <!--
        form 태그의 기본 전송 content type은 application/x-www-form-urlencoded 으로 설정 되어 있으나
        file을 전송할 경우에는 encoding type을 multipart/form-data로 변경해야 한다.
    -->
    
    <!--위에서 작성한 내용 생략-->
 
    <h3>multi file upload</h3>
    <form action="multi-file" method="post" enctype="multipart/form-data">
        파일 : <input type="file" name="multiFiles" multiple><br>  <!--multiple은 파일 여러개를 한 번에 업로드하기 위한 속성이다.-->
        파일 설명 : <input type="text" name="multiFilesDescription"><br>
        <input type="submit">
    </form>
</body>
</html>

2. 파일 업로드를 처리할 controller 생성하기

위에서 작성한 FileUploadController에 코드를 추가해주었다.

업로드 파일이 같은 name 속성으로 여러 개 전달되므로 MultipartFile 클래스는 List 타입으로 선언해야 한다.

 

@RequestParam 어노테이션을 이용하여

요청 파라미터 중 multiFiles라는 이름으로 전송된 파일을 List<MultipartFile> 객체로 받아온다.

이후 해당 파일을 처리하는데  필요한 정보를 DTO 타입을 선언해서 다룬다.

추후 DB에 저장하는 등의 작업을 수행하는데,

실패 시 이전에 저장된 파일은 삭제하고 최종적으로 result라는 view 페이지를 반환한다. (위에서 작)

package org.example.fileupload;

@Controller
public class FileUploadController {

    // 위에서 작성한 코드 생략
    
    @PostMapping("/multi-file")
    public String multiFileUpload(
            @RequestParam String multiFilesDescription,
            @RequestParam List<MultipartFile> multiFiles,
            Model model
            ) {

        /* 서버로 전송 된 MultipartFile 객체를 서버에서 지정하는 경로에 저장한다. */
        String root = "src/main/resources/static";
        String filePath = root + "/uploadFiles";
        File dir = new File(filePath);

        if(!dir.exists()) dir.mkdirs();

        List<FileDTO> files = new ArrayList<>();

        try {

            /* 다중 파일이므로 반복문을 이용한 처리를 하고 저장한다. */
            for(MultipartFile multiFile : multiFiles) {

                /* 파일명이 중복 되면 해당 경로에서 덮어쓰기 될 수 있으므로 중복 되지 않는 이름으로 변경 */
                String originFileName = multiFile.getOriginalFilename();
                String ext = originFileName.substring(originFileName.lastIndexOf("."));
                String savedName = UUID.randomUUID() + ext;

                /* 파일 저장 */
                multiFile.transferTo(new File(filePath + "/" + savedName));

                /* 파일에 관한 정보를 FileDTO에 담아 List에 보관 */
                files.add(new FileDTO(originFileName, savedName, filePath, multiFilesDescription));

            }

            // files 정보는 DB에 insert 된다.
            model.addAttribute("message", "다중 파일 업로드 완료");

        } catch (IOException e) {
            /* 파일 저장이 중간에 실패할 경우 이전에 저장된 파일 삭제 */
            for(FileDTO file : files) new File(filePath + "/" + file.getSavedFileName()).delete();
            model.addAttribute("message", "다중 파일 업로드 실패");
//            throw new RuntimeException(e);
        }

        return "result";
    }
}

3. FileDTO

package org.example.fileupload;

/* 업로드 된 파일과 관련한 정보를 모아서 관리하는 DTO 클래스 */
public class FileDTO {

    private String originFileName;
    private String savedFileName;
    private String filePath;
    private String fileDescription;

    public FileDTO(String originFileName, String savedFileName, String filePath, String fileDescription) {
        this.originFileName = originFileName;
        this.savedFileName = savedFileName;
        this.filePath = filePath;
        this.fileDescription = fileDescription;
    }

    public String getOriginFileName() {
        return originFileName;
    }

    public void setOriginFileName(String originFileName) {
        this.originFileName = originFileName;
    }

    public String getSavedFileName() {
        return savedFileName;
    }

    public void setSavedFileName(String savedFileName) {
        this.savedFileName = savedFileName;
    }

    public String getFilePath() {
        return filePath;
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }

    public String getFileDescription() {
        return fileDescription;
    }

    public void setFileDescription(String fileDescription) {
        this.fileDescription = fileDescription;
    }

    @Override
    public String toString() {
        return "FileDTO{" +
                "originFileName='" + originFileName + '\'' +
                ", savedFileName='" + savedFileName + '\'' +
                ", filePath='" + filePath + '\'' +
                ", fileDescription='" + fileDescription + '\'' +
                '}';
    }
}

 

위의 과정을 통해 지정된 경로에 파일이 업로드 되는 것을 확인할 수 있다.