https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/multipart.html
파일 업로드는 클라이언트가 자신의 디바이스에서 서버로 파일 데이터를 전송하는 과정입니다.
Spring MVC는 파일 업로드를 처리하기 위해 Multipart 요청을 지원하며, 파일 데이터와 함께 일반 폼 데이터도 처리할 수 있습니다.
multipart/form-data로 지정된 요청.POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="username"
testuser
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="example.txt"
Content-Type: text/plain
This is the content of the file.
------WebKitFormBoundary--
Spring은 Commons FileUpload 또는 Spring Web의 내장 MultipartResolver를 사용하여 Multipart 요청을 처리합니다.
Spring Boot에서는 기본적으로 StandardServletMultipartResolver가 사용됩니다.
Spring Boot는 기본적으로 파일 업로드를 지원하므로 추가 의존성이 필요하지 않습니다.
(단, 특별히 Apache Commons FileUpload를 사용할 경우 관련 의존성 추가 필요)
파일 업로드 크기 제한과 같은 설정을 정의합니다.
# 최대 파일 크기 설정
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=20MB
Spring에서 파일 업로드 요청은 MultipartFile 객체로 처리됩니다.
| 메서드 | 설명 |
|---|---|
getName() | 요청에서 필드 이름 반환 |
getOriginalFilename() | 업로드된 파일 이름 반환 |
getBytes() | 파일 데이터를 바이트 배열로 반환 |
getInputStream() | 파일 데이터를 읽기 위한 InputStream 반환 |
transferTo(File dest) | 파일 데이터를 지정된 경로에 저장 |
<form action="/upload" method="post" enctype="multipart/form-data">
<label for="file">파일 업로드:</label>
<input type="file" name="file" id="file">
<button type="submit">업로드</button>
</form>
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@RestController
public class FileUploadController {
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return "파일을 선택하지 않았습니다.";
}
try {
// 저장 경로 설정
String uploadDir = "uploads/";
File dest = new File(uploadDir + file.getOriginalFilename());
file.transferTo(dest); // 파일 저장
return "업로드 성공: " + dest.getAbsolutePath();
} catch (IOException e) {
e.printStackTrace();
return "업로드 실패";
}
}
}
<form action="/multi-upload" method="post" enctype="multipart/form-data">
<label for="files">파일 업로드:</label>
<input type="file" name="files" id="files" multiple>
<button type="submit">업로드</button>
</form>
import java.util.List;
@PostMapping("/multi-upload")
public String uploadMultipleFiles(@RequestParam("files") List<MultipartFile> files) {
StringBuilder result = new StringBuilder();
for (MultipartFile file : files) {
if (!file.isEmpty()) {
try {
String uploadDir = "uploads/";
File dest = new File(uploadDir + file.getOriginalFilename());
file.transferTo(dest); // 파일 저장
result.append("업로드 성공: ").append(dest.getAbsolutePath()).append("<br>");
} catch (IOException e) {
e.printStackTrace();
result.append("업로드 실패: ").append(file.getOriginalFilename()).append("<br>");
}
}
}
return result.toString();
}
application.properties에서 설정:spring.servlet.multipart.max-file-size=5MB
spring.servlet.multipart.max-request-size=10MB
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MaxUploadSizeExceededException.class)
public String handleMaxSizeException(MaxUploadSizeExceededException ex) {
return "파일 크기가 제한을 초과했습니다.";
}
}
파일 업로드 시 저장 위치를 선택하는 것은 애플리케이션의 성능, 데이터 관리, 확장성 등에 큰 영향을 미칩니다. 저장 위치를 선택할 때 아래와 같은 기준을 고려합니다.
spring.servlet.multipart.location=/var/www/uploads
spring.servlet.multipart.location=//network-storage/shared/uploads
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.PutObjectRequest;
@RestController
public class FileUploadController {
private final AmazonS3 amazonS3;
public FileUploadController(AmazonS3 amazonS3) {
this.amazonS3 = amazonS3;
}
@PostMapping("/upload")
public String uploadToS3(@RequestParam("file") MultipartFile file) throws IOException {
String bucketName = "your-bucket-name";
amazonS3.putObject(new PutObjectRequest(bucketName, file.getOriginalFilename(), file.getInputStream(), null));
return "파일이 S3에 업로드되었습니다.";
}
}
BLOB 또는 CLOB 필드에 저장.@Repository
public interface FileRepository extends JpaRepository<FileEntity, Long> {}
@Entity
public class FileEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String fileName;
@Lob
private byte[] data;
// Getters and Setters
}
@PostMapping("/upload")
public String uploadToDb(@RequestParam("file") MultipartFile file) throws IOException {
FileEntity fileEntity = new FileEntity();
fileEntity.setFileName(file.getOriginalFilename());
fileEntity.setData(file.getBytes());
fileRepository.save(fileEntity);
return "파일이 데이터베이스에 저장되었습니다.";
}
spring.servlet.multipart.enabled=true
spring.servlet.multipart.location=/var/uploads
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=50MB
enabled: Multipart 요청 처리 활성화.location: 파일 임시 저장 경로.max-file-size: 업로드 파일 하나의 최대 크기.max-request-size: 요청 전체 크기(파일 + 폼 데이터 포함).저장 경로를 동적으로 변경하려면 MultipartConfigElement를 사용합니다.
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setLocation("/dynamic/uploads");
factory.setMaxFileSize(DataSize.ofMegabytes(10)); // 10MB
factory.setMaxRequestSize(DataSize.ofMegabytes(50)); // 50MB
return factory.createMultipartConfig();
}
파일 업로드 시 저장 경로가 존재하지 않으면 동적으로 생성해야 합니다.
@PostConstruct
public void createUploadDir() {
File uploadDir = new File("/var/uploads");
if (!uploadDir.exists()) {
uploadDir.mkdirs(); // 디렉토리 생성
}
}
같은 이름의 파일이 업로드되면 덮어씌워지는 문제가 발생할 수 있습니다. 이를 방지하기 위해 파일 이름을 고유하게 변경합니다.
String uniqueFileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename();
File dest = new File("/var/uploads/" + uniqueFileName);
file.transferTo(dest);
특정 파일 형식만 업로드 가능하도록 설정하여 악성 파일 업로드를 방지.
String contentType = file.getContentType();
if (!contentType.equals("image/png") && !contentType.equals("image/jpeg")) {
throw new IllegalArgumentException("허용되지 않은 파일 형식입니다.");
}
업로드된 파일이 실행되지 않도록 별도의 디렉토리에 저장하고, 실행 권한을 제거.
chmod -R 700 /var/uploads
application.properties에서 크기 제한 설정 외에도 추가 검증을 수행.
if (file.getSize() > 10 * 1024 * 1024) { // 10MB 제한
throw new IllegalArgumentException("파일 크기가 너무 큽니다.");
}
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@RestController
@RequestMapping("/file")
public class FileController {
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return "파일이 선택되지 않았습니다.";
}
try {
String uploadDir = "/var/uploads/";
String uniqueFileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename();
File dest = new File(uploadDir + uniqueFileName);
file.transferTo(dest);
return "파일 업로드 성공: " + dest.getAbsolutePath();
} catch (IOException e) {
e.printStackTrace();
return "파일 업로드 실패";
}
}
}
| 저장 위치 | 적합한 환경 | 예시 사용 사례 |
|---|---|---|
| 로컬 파일 시스템 | 단일 서버, 초기 개발 환경 | 간단한 이미지 업로드, 로그 파일 저장 |
| 외부 저장소 | 다중 서버, 고가용성 환경 | 대규모 웹 애플리케이션, 공유 문서 저장 시스템 |
| 클라우드 스토리지 | 글로벌 확장성, 무제한 저장소 필요 | 전자상거래, 사용자 생성 콘텐츠 플랫폼 |
| 데이터베이스 | 데이터 일관성, 파일 메타데이터와 함께 저장 필요 | 프로필 이미지, 보안 데이터 관리 |