반응형
파일 업로드는 웹 애플리케이션에서 굉장히 자주 사용되는 기능입니다. 이번 글에서는 스프링 MVC에서 파일 업로드를 구현하는 전 과정을 코드와 함께 자세히 설명합니다. 특히 초보자도 이해할 수 있도록 **"왜 이 코드가 필요한가?"**를 중심으로 설명합니다.
✅ HTML 폼의 전송 방식 이해
파일 업로드를 하기 전에 먼저 HTML <form> 태그가 데이터를 전송하는 방식을 알아야 합니다.
text
복사편집
1. application/x-www-form-urlencoded ← 기본값 (텍스트 데이터만 전송 가능) 2. multipart/form-data ← 파일 포함한 데이터 전송 가능 (우리가 쓸 방식!)
- application/x-www-form-urlencoded는 텍스트만 전송 가능.
- multipart/form-data는 텍스트 + 파일(바이너리) 함께 전송 가능.
🔥 왜 multipart/form-data를 써야 할까?
폼에서 이름, 나이 같은 텍스트 + 파일을 동시에 보내야 하기 때문입니다. 그래서 form 태그에 반드시 다음 속성을 추가합니다:
html
복사편집
<form method="post" enctype="multipart/form-data">
🏗 프로젝트 초기 설정 (Spring Boot)
groovy
복사편집
// build.gradle dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' }
- Web, Thymeleaf, Lombok 사용
- Java 11 기준으로 진행
📄 HTML 업로드 폼 작성 (upload-form.html)
html
복사편집
<form th:action method="post" enctype="multipart/form-data"> <ul> <li>상품명 <input type="text" name="itemName"></li> <li>파일<input type="file" name="file"></li> </ul> <input type="submit"/> </form>
- enctype="multipart/form-data" 꼭 필요!
- 파일 input은 <input type="file" name="file">
① 서블릿 기반 파일 업로드 (ServletUploadControllerV1)
java
복사편집
@PostMapping("/upload") public String saveFileV1(HttpServletRequest request) throws ServletException, IOException { String itemName = request.getParameter("itemName"); Collection<Part> parts = request.getParts(); // ★ 핵심! multipart 파싱 ... }
🧠 왜 이 코드를 쓰는가?
- request.getParts() → 전송된 각 파트를 분리해 Part 객체로 제공
- Part → 파일, 텍스트 구분 없이 다 들어있음
application.properties 추가
properties
복사편집
logging.level.org.apache.coyote.http11=trace
- HTTP 전송 내용을 콘솔에서 확인 가능
② 파일을 실제로 서버에 저장하기 (ServletUploadControllerV2)
java
복사편집
@Value("${file.dir}") private String fileDir; // application.properties에서 업로드 경로 주입 @PostMapping("/upload") public String saveFileV1(HttpServletRequest request) throws ServletException, IOException { Collection<Part> parts = request.getParts(); for (Part part : parts) { if (StringUtils.hasText(part.getSubmittedFileName())) { String fullPath = fileDir + part.getSubmittedFileName(); part.write(fullPath); // ★ 서버에 저장 } } ... }
📌 주의할 점
- file.dir은 실제 폴더 경로여야 하며, 미리 만들어져 있어야 합니다.
- 저장할 때 part.write() 메서드를 사용.
③ 스프링 방식으로 업로드 처리 (SpringUploadController)
java
복사편집
@PostMapping("/upload") public String saveFile(@RequestParam String itemName, @RequestParam MultipartFile file) throws IOException { if (!file.isEmpty()) { String fullPath = fileDir + file.getOriginalFilename(); file.transferTo(new File(fullPath)); // ★ 스프링식 파일 저장 } ... }
✅ 스프링은 MultipartFile을 자동 주입해줍니다
- @RequestParam MultipartFile file로 선언만 하면 파일이 들어옴
- file.getOriginalFilename() → 업로드한 원본 파일명
- file.transferTo(...) → 저장
④ 실전 예제 - 첨부파일 + 이미지 여러 개 업로드 (ItemController)
📌 핵심 기능
- 첨부파일 1개 업로드
- 이미지 여러 개 업로드
- 업로드 파일 정보 저장
- 업로드 파일 다운로드 + 이미지 출력
📦 관련 클래스 요약
클래스설명
Item | 상품 엔티티, 첨부파일과 이미지 목록 포함 |
UploadFile | 업로드된 파일 정보 (원본명, 저장명) |
FileStore | 파일 저장 로직 처리 (UUID 생성 포함) |
ItemRepository | 메모리 저장소 (Map 사용) |
ItemForm | 폼 객체 (파일 정보 포함) |
ItemController | 업로드 처리 컨트롤러 |
📁 FileStore.java – 파일 저장 처리
java
복사편집
public UploadFile storeFile(MultipartFile multipartFile) throws IOException { String originalFilename = multipartFile.getOriginalFilename(); String storeFileName = createStoreFileName(originalFilename); multipartFile.transferTo(new File(getFullPath(storeFileName))); return new UploadFile(originalFilename, storeFileName); } private String createStoreFileName(String originalFilename) { String ext = extractExt(originalFilename); return UUID.randomUUID().toString() + "." + ext; }
🔐 왜 UUID를 붙여서 저장할까?
- 사용자가 같은 파일명을 여러 번 업로드하면 덮어쓰기 발생
- 서버에서 고유한 이름으로 관리해야 함
🌅 이미지 출력 및 파일 다운로드
이미지 출력
java
복사편집
@GetMapping("/images/{filename}") @ResponseBody public Resource downloadImage(@PathVariable String filename) { return new UrlResource("file:" + fileStore.getFullPath(filename)); }
- <img> 태그에서 호출되며, 서버에서 파일 바이너리를 직접 반환
첨부파일 다운로드
java
복사편집
@GetMapping("/attach/{itemId}") public ResponseEntity<Resource> downloadAttach(@PathVariable Long itemId) { Item item = itemRepository.findById(itemId); String storeFileName = item.getAttachFile().getStoreFileName(); String uploadFileName = item.getAttachFile().getUploadFileName(); UrlResource resource = new UrlResource("file:" + fileStore.getFullPath(storeFileName)); String encodedUploadFileName = UriUtils.encode(uploadFileName, StandardCharsets.UTF_8); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + encodedUploadFileName + "\"") .body(resource); }
- 파일 다운로드 시 Content-Disposition 헤더로 다운로드 파일명 설정 가능
📌 마무리 요약
구분방식설명
서블릿 방식 | HttpServletRequest, Part | 직접 파싱 |
스프링 방식 | MultipartFile | 자동 처리 (권장) |
저장 방식 | file.transferTo() or part.write() | 서버 저장 |
주의사항 | 파일명 중복 방지 | UUID 사용 |
응답 처리 | @ResponseBody, Resource | 이미지, 첨부파일 반환 |
📍 실습 주소 예시
주소설명
/spring/upload | 스프링 파일 업로드 |
/items/new | 상품 업로드 폼 |
/items/{id} | 상품 상세 보기 |
/images/{filename} | 이미지 조회 |
/attach/{itemId} | 첨부파일 다운로드 |
💡 이 포스트는 스프링 파일 업로드의 전 과정을 실습 + 설명한 가이드입니다. 초보자라면 꼭 직접 실습해보세요!