본문 바로가기

Web/Spring

[Spring] 파일 업로드/다운로드 (업로드)

728x90
반응형

오늘은 웹 개발에서 거의 빼놓을 수 없는 파일 업로드/다운로드에 대해서 알아보자.

 

이 전에 한 번 다룬 적이 있다.

그땐 Servlet을 이용해서 파일 업로드/다운로드를 다뤘다.

하지만 Spring을 이용하여 개발한다면 훨씬 편하게 파일을 다룰 수 있다.

 

Spring을 이용한 방법으로 알아보자.

 

이 전에 Servlet을 이용한 파일 업로드/다운로드는 링크로 남겨두자.

 

https://thecardeveloper.tistory.com/7

 

파일 업로드 / 다운로드 (업로드)

오늘은 웹 프로젝트를 진행하면서 어떠한 프로젝트에도 거의 대부분 사용되는 파일 업로드/ 다운로드 기능을 알아볼까한다. 우선 html 코드이다. 파일을 업로드 할 때 form을 주로 사용하는데 위

thecardeveloper.tistory.com

 

https://thecardeveloper.tistory.com/8

 

파일 업로드 / 다운로드 (다운로드)

이번에는 파일 다운로드 녀석이다. 우선 파일을 다운로드 하려면 저장된 파일 이름을 가져와야한다.(당연한 소리다.) 이전 업로드 글에서 얘기했듯이 db에는 originalFileName과 saveFileName이 들어있다

thecardeveloper.tistory.com

 

요즘 웹 서버 개발 시에 Spring을 사용하지 않는 프로젝트는 거의 없는 거 같다.

물론 레거시 프로젝트나 SI 업체에서 진행하는 프로젝트에는 간혹 Spring을 사용하지 않기도 하는 거 같다.

 

 

자 우선 HTML에서 Form을 사용하여 file을 서버로 보내보자.

  <form th:action method="post" enctype="multipart/form-data">
        <ul>
            <li>상품명 <input type="text" name="itemName"></li>
            <li>단일<input type="file" name="attachFile" ></li>
            <li>복수 파일들<input type="file" multiple="multiple" name="imageFiles" ></li>
        </ul>
        <input type="submit"/>
    </form>

전송 데이터 

1. 상품명 : itemName

2. 단일파일 : attachFile

3 복수 파일 : imageFiles

 

위에서 눈여겨봐야 하는 부분은

1. enctype="multipart/form-data" -> formData 전송 방식

2. type = "file" -> 전송 데이터가 file 형식

3. multiple="multiple" -> 단일 파일이 아닌 여러 개 복수 파일을 전송

 

다음은 컨트롤러에서 데이터를 받아보자.

ItemController.java

1. @ModelAttribute를 이용하여 formDataItemForm 객체로 받음(Form을 이용한 post 방식이기 때문에 가능)

2. 파일 업로드 로직이 끝난 이후 redirect 정보를 저장하여 redirect 하기 위한 RedirectAttributes 추가로 받음

    @PostMapping("/items/new")
    public String saveItem(@ModelAttribute ItemForm form, RedirectAttributes redirectAttributes) throws IOException {
        UploadFile attachFile = fileStore.storeFile(form.getAttachFile());
        List<UploadFile> storeImageFiles = fileStore.storeFiles(form.getImageFiles());

        //데이터베이스에 저장
        Item item = new Item();
        item.setItemName(form.getItemName());
        item.setAttachFile(attachFile);
        item.setImageFiles(storeImageFiles);
        itemRepository.save(item);

        redirectAttributes.addAttribute("itemId", item.getId());
        return "redirect:/items/{itemId}";
    }

 

ItemForm.java

1. 전송 데이터 바인딩 객체

2. form에서 보내는 nameItemForm필드 이름 맞춰주기

3. 단일 file은 attachFile에 바인딩, 복수 파일들은 imageFiles에 바인딩

@Data
public class ItemForm {

    private Long itemId;
    private String itemName;
    private MultipartFile attachFile;
    private List<MultipartFile> imageFiles;

}

Item.java

1. 실제 저장 domain

@Data
public class Item {

    private Long id;
    private String itemName;
    private UploadFile attachFile;
    private List<UploadFile> imageFiles;
}

 

ItemRepository.java

1. 실제 Database 이용이 아닌 메모리 data 사용을 위해 store 생성

2. 메모리 data(store)에 저장 및 findById 메서드 생성

@Repository
public class ItemRepository {

    private final Map<Long, Item> store = new HashMap<>();
    private long sequence = 0L;

    public Item save(Item item) {
        item.setId(++sequence);
        store.put(item.getId(), item);
        return item;
    }

    public Item findById(Long id) {
        return store.get(id);
    }
}

 

FileStore.java

1. file 업로드/다운로드를 위한 공통 로직 class

2. application.properties에 file.dir 선언 (실제 파일 업로드 경로)

3. 파일 등록 경로 생성을 위한 getFullPath 메서드 생성

4. 복수 파일 upload를 위한 storeFiles 메서드 생성

5. 단일 파일 upload를 위한 storeFile 메서드 생성

6. 저장되는 파일의 이름 중복을 방지하기 위한 createStoreFileName 메서드 생성 (UUID사용, 확장자 유지)

7. 파일 확장자를 위한 extracted 메서드 생성

@Component
public class FileStore {

    @Value("${file.dir}")
    private String fileDir;

    public String getFullPath(String fileName) {
        return fileDir + fileName;
    }

    public List<UploadFile> storeFiles(List<MultipartFile> multipartFiles) throws IOException {
        List<UploadFile> storeFileResult = new ArrayList<>();
        for (MultipartFile multipartFile : multipartFiles) {
            if (!multipartFile.isEmpty()) {
                storeFileResult.add(storeFile(multipartFile));
            }
        }
        return storeFileResult;
    }

    public UploadFile storeFile(MultipartFile multipartFile) throws IOException {
        if (multipartFile.isEmpty()) {
            return null;
        }

        String originalFilename = multipartFile.getOriginalFilename();
        //image.png

        String storeFileName = createStoreFileName(originalFilename);
        
        //실제로 파일이 지정되 서버 경로에 upload
        multipartFile.transferTo(new File(getFullPath(storeFileName)));

        return new UploadFile(originalFilename, storeFileName);
    }

    private String createStoreFileName(String originalFilename) {
        String ext = extracted(originalFilename);
        String uuid = UUID.randomUUID().toString();
        return uuid + "." + ext;
    }

    private String extracted(String originalFilename) {
        int pos = originalFilename.lastIndexOf(".");
        return originalFilename.substring(pos + 1);
    }

}

 

 

위와 같은 로직으로 Spring을 이용하여 파일 업로드가 가능하다.

물론 코드상에서 예외처리나 보안상의 문제 등을 더욱 보완 해야하지만

확실히 코드상으로도 Spring을 이용하고 안하고의 차이가 많이 나는 것을 알 수 있었다.

 

정말 많은 부분을 Spring이 개발자를 대신해주고 있다.

 

더욱 깊이는 inflearn 김영한의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술을 참고하자.

 

 

전혀 돈이 아깝지 않은 강의이니 나를 위해 투자하자.

728x90
반응형