Spring Boot에서 파일 업로드 및 저장 로직

차윤하·2025년 5월 4일
0

웹 서비스에서 파일 업로드 기능은 매우 자주 사용된다. 이번 글에서는 Spring Boot에서 파일 업로드 시 처리해야 할 주요 작업들과 실제 파일 저장 로직, 그리고 유효성 검증 및 예외 처리에 대해 정리해보려고 한다.


왜 파일명을 변경해서 저장해야 할까?

업로드된 파일을 서버에 저장할 때 원래 파일명을 그대로 저장하면, 같은 이름의 파일이 업로드될 경우 기존 파일이 덮어 써지는 문제가 발생한다. 이를 방지하기 위해 UUID를 활용하여 고유한 파일명을 생성해 저장해주어야 한다.

String changedName = UUID.randomUUID().toString() + "." + extension;

파일 저장 전 처리해야 할 작업들

1. 파일 확장자 추출하기

파일 확장자는 파일명에서 마지막 . 이후의 문자열로 얻을 수 있다.

String extension = originalName.substring(originalName.lastIndexOf(".") + 1);

lastIndexOf(".")를 사용하면 파일명 중 마지막 점(.)의 위치를 기준으로 확장자를 정확히 추출할 수 있다.


2. 파일 저장 경로 만들기

파일은 디스크에 저장할 때 폴더 + 파일명으로 경로가 완성되어야 한다.
이 경로를 코드에 하드코딩하기보다는 application.yml에 저장하고 @Value로 주입받는 게 유지보수에 유리하다.

application.yml

file:
  upload-dir: C:/uploads/myfile/files

코드에서 주입받기

@Value("${file.upload-dir}")
private String uploadUrl;

하드코딩(String path = "C:/...")보다 설정 파일을 사용하는 것이 환경 변경에 유리하고, 코드 수정 없이 배포 가능하다.


3. 파일 디렉토리 생성

업로드 디렉토리가 존재하지 않으면 직접 만들어주어야 한다.

File directory = new File(uploadUrl);
if (!directory.exists()) {
    directory.mkdirs();
}

실제 파일 저장

업로드된 파일을 서버 디스크에 저장하려면 transferTo() 메서드를 꼭 호출해줘야 한다. 이걸 하지 않으면 DB에 경로만 저장되고 실제 파일은 존재하지 않는 상황이 생긴다.

File saveFile = new File(uploadUrl + File.separator + changedName);
file.transferTo(saveFile); // 서버 디스크에 저장

디렉토리가 존재하지 않는다면 mkdirs()로 먼저 만들어줘야 한다.


파일 저장 서비스 전체 로직

@Transactional
public Object createPostWithFile(PostRequestDTO postRequestDTO, List<MultipartFile> files) {
    Post post = new Post(postRequestDTO.getTitle(), postRequestDTO.getContent());

    if (!files.isEmpty()) {
        File directory = new File(uploadUrl);
        if (!directory.exists()) {
            directory.mkdirs();
        }

        for (MultipartFile file : files) {
            validateFile(file);

            String originalName = file.getOriginalFilename();
            String extension = originalName.substring(originalName.lastIndexOf(".") + 1);
            String changedName = UUID.randomUUID().toString() + "." + extension;
            String path = uploadUrl + File.separator + changedName;

            try {
                file.transferTo(new File(path));
            } catch (IOException e) {
                throw new RuntimeException("파일 저장 실패", e);
            }

            Attachment newAttachment = Attachment.builder()
                    .name(originalName)
                    .changedName(changedName)
                    .path(path)
                    .size(file.getSize())
                    .extension(extension)
                    .build();

            post.addAttachment(newAttachment);
        }
    }

    postRepository.save(post);
    return "저장 완료";
}

파일 유효성 검사

업로드 가능한 파일에 대해 몇 가지 기준을 두고 필터링해야 한다.
아래 3가지는 기본적으로 검사해주는 게 좋다.

유효성 검사 항목

  • 파일명 검사: 비어있거나 null인 경우 거부
  • 파일 크기 제한: 예: 100MB 이하
  • 확장자 제한: .jpg, .png, .pdf 등 허용된 확장자만 업로드 가능
private void validateFile(MultipartFile file) {
    if (file.getOriginalFilename() == null) {
        throw new IllegalArgumentException("유효하지 않은 파일명입니다.");
    }

    if (file.getSize() > MAX_FILE_SIZE) {
        throw new IllegalArgumentException("파일 크기는 100MB를 초과할 수 없습니다.");
    }

    String extension = file.getOriginalFilename()
                           .substring(file.getOriginalFilename().lastIndexOf(".") + 1)
                           .toLowerCase();

    if (!ALLOWED_EXTENSIONS.contains(extension)) {
        throw new IllegalArgumentException("허용되지 않은 파일 확장자입니다. 허용 확장자: " + ALLOWED_EXTENSIONS);
    }
}

예외 처리

잘못된 요청에 대해서는 IllegalArgumentException 등 구체적인 예외를 던져, 클라이언트가 문제를 쉽게 파악할 수 있도록 한다.
IllegalArgumentException:인자가 잘못되었을 때 발생하는 예외


정리

정리한 파일 업로드 로직은 다음과 같은 요구사항을 충족한다:

  • 중복 파일명 문제 방지 (UUID)
  • 유연한 경로 관리 (설정 파일)
  • 안전한 저장 (유효성 검사 및 예외 처리)
  • 실제 디스크 저장 보장
profile
풀스택 개발자가 되고 싶은 꿈나무

0개의 댓글