새발블로그

[Spring] 파일 업로드 & 다운로드 본문

Server/Spring

[Spring] 파일 업로드 & 다운로드

EUG 2025. 9. 22. 00:16

1. 기본 파일 업로드 구조

Spring에서는 MultipartFile을 사용해 업로드된 파일을 처리합니다.
HTML form 태그에서 반드시 enctype="multipart/form-data"를 지정해야 합니다.

<form method="post" enctype="multipart/form-data">
    <input type="file" name="file" />
    <button type="submit">Upload</button>
</form>
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) throws IOException {
    if (!file.isEmpty()) {
        String originalName = file.getOriginalFilename();
        file.transferTo(new File("/upload/dir/" + originalName));
    }
    return "success";
}

 

2. 기본 파일 다운로드 구조

다운로드는 ResponseEntity<Resource>를 이용해 구현합니다.

@GetMapping("/download/{filename}")
public ResponseEntity<Resource> download(@PathVariable String filename) throws IOException {
    Path path = Paths.get("/upload/dir/" + filename);
    Resource resource = new InputStreamResource(Files.newInputStream(path));

    return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .body(resource);
}

 

3. 파일명 충돌 방지와 UID(고유 ID)

업로드에서 중요한 포인트 중 하나는 파일명 충돌 방지입니다.
여러 사용자가 동일한 이름(image.png)으로 업로드할 경우를 대비해야 합니다.

-> 해결책 : UUID (Universally Unique Identifier)

String originalFilename = file.getOriginalFilename();
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
String uid = UUID.randomUUID().toString(); // 고유 UID 생성
String savedName = uid + extension;
 

왜 UID가 중요한가?

  • 파일 고유 식별자: 파일 시스템뿐만 아니라 API 호출 시에도 안정적으로 파일을 식별 가능
  • 외부 API 연동: CLOVA OCR 같은 서비스는 업로드 시 uid 필드를 요구하는데, 이는 요청을 구분하기 위한 필수 식별값
  • DB 관리 용이: 원본 파일명과 별개로 UID를 저장하면 충돌 없이 안정적 관리 가능

예시 DB 스키마:

칼럼 설명
id (PK) DB 내부 식별자
uid UUID 기반 고유값
original_name 사용자가 업로드한 원본 이름
saved_path 실제 저장된 경로
size 파일 크기

4. 업로드 디렉토리 구조화

파일이 많아지면 관리가 어려워집니다.
-> 날짜별 디렉토리 분리 방식 권장 (/2025/09/21/uid.png)

public static String makeFolderByDate(String baseDir) {
    String folderPath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
    Path uploadPath = Paths.get(baseDir, folderPath);
    Files.createDirectories(uploadPath);
    return uploadPath.toString();
}

 

5. 업로드 예외 처리와 롤백

문제:

  • 파일은 디스크에 저장됨
  • DB INSERT는 MyBatis로 처리됨
  • 중간에 예외 발생 시 DB는 @Transactional로 롤백되지만, 파일은 남아버림 ⚠️

해결책: 업로드 성공 파일을 List에 저장해두고, 예외 발생 시 직접 삭제.

@Transactional
public void upload(Long bno, List<MultipartFile> files) {
    List<String> uploadedPaths = new ArrayList<>();

    try {
        for (MultipartFile file : files) {
            String path = UploadFiles.upload(BASE_DIR, file);
            uploadedPaths.add(path);

            BoardAttachmentVO attach = BoardAttachmentVO.of(file, bno, path);
            mapper.createAttachment(attach);
        }
    } catch (Exception e) {
        // 업로드된 파일 삭제
        for (String path : uploadedPaths) {
            Files.deleteIfExists(Paths.get(path));
        }
        throw new RuntimeException("업로드 실패", e); // rollback 유도
    }
}
 

'Server > Spring' 카테고리의 다른 글

[Spring] AOP (Aspect Oriented Programming)  (0) 2025.09.22
[Spring] 직렬화와 역직렬화  (0) 2025.09.22
[Spring] Spring + MyBatis  (0) 2025.09.22
[Spring] Spring 어노테이션  (0) 2025.07.11
[Spring] Spring MVC  (0) 2025.07.08