쌓고 쌓다

[스프링 부트] 파일 업로드 - 17 본문

프로그래밍/spring

[스프링 부트] 파일 업로드 - 17

승민아 2023. 8. 2. 18:55

upload_file 테이블

create table upload_file (
    id bigint auto_increment, 
    pno bigint, 
    upload_file_name VARCHAR(255), 
    store_file_name VARCHAR(255), 
    PRIMARY KEY (id), 
    FOREIGN KEY (pno) REFERENCES poster(id)
);

파일명 중복을 방지하고자 시스템상 저장하는 파일명 store_file_name이 존재한다.

 

UploadFile

@Entity
@NoArgsConstructor
@Getter
@Setter
public class UploadFile {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Long pno;

    private String uploadFileName; // 업로드한 파일명
    private String storeFileName; // 시스템에 저장한 파일명

    public UploadFile(String uploadFileName, String storeFileName) {
        this.uploadFileName = uploadFileName;
        this.storeFileName = storeFileName;
    }
}

DB에서 저장할 파일에 대한 클래스이다.

파일명을 두개로 나눈 이유는 서버에 저장될 파일이름이 중복되는경우 문제가 발생하기에

시스템에 저장할 파일명을 따로 저장해두는것이다.

 

Poster

public class Poster {
    @OneToMany
    @JoinColumn(name="pno")
    private List<UploadFile> imgFiles;
}

 

게시글에서 이제 일대다 단방향으로 연관관계를 맺었다.

외래 키 관리는 Poster가 한다.

@JoinColumn의 name 속성의 값을 상대 테이블의 컬럼명으로 해주어 외래 키를 Poster가 관리하게 한다.

 

UploadFileRepository

public interface UploadFileRepository extends JpaRepository<UploadFile, Long> {
}

 

application.properties

file.dir = /Users/lsm/Desktop/imgFolder/

@Value 어노테이션으로 프로퍼티를 읽어 파일 저장 경로를 읽을 예정이다.

 

FileStore

@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> storeResult = new ArrayList<>();

        for(MultipartFile multipartFile : multipartFiles) {
            String uploadFileName = multipartFile.getOriginalFilename();

            String uuid = UUID.randomUUID().toString();
            int pos = uploadFileName.indexOf(".");
            String ext = uploadFileName.substring(pos + 1);

            String storeFileName = uuid + "." + ext;

            multipartFile.transferTo(new File(getFullPath(storeFileName)));
            storeResult.add(new UploadFile(uploadFileName, storeFileName));
        }
        return storeResult;
    }
}

파일 경로 필드와 메서드 기능 두개를 가진다.

getFullPath : 파일 경로와 파일 이름을 합쳐 저정할 경로와 파일명까지 전체경로를 반환한다.

storeFiles : List<MultipartFile>을 받아 파일들을 전체경로로 저장하고 List<UploadFile>로 변환하여 반환한다

storeFiles 메서드에서 UUID를 통해 시스템 저장 파일명과 원래 파일명을 저장한다.

 

createPosterForm.html

<form action="/poster/write" method="post" enctype="multipart/form-data">
    ...
    <input type="file" name="files" multiple="multiple">
</form>

enctype="multipart/form-data": 여러 종류의 데이터를 여러 부분으로 나눠 전송할 때 사용한다. 

boundary로 메시지 파트를 나눈다.

 

PosterController

@Controller
public class PosterController {

    private FileStore fileStore;
    private UploadFileRepository uploadFileRepository;

    @PostMapping("/poster/write")
    public String write(@RequestParam(required = false) List<MultipartFile> files, @Valid Poster poster ,Errors errors) throws IOException {
        if(errors.hasErrors()) {
            return "posters/createPosterForm";
        }

        List<UploadFile> uploadFiles = fileStore.storeFiles(files);
        uploadFileRepository.saveAll(uploadFiles);
        poster.setImgFiles(uploadFiles);
        posterService.write(poster);
        return "redirect:/";
    }

}

form을 통해 넘어온 files를

storeFiles 메서드를 통해 서버 폴더에 파일을 저장하고 반한된 List<UploadFile>를 DB에 저장하고 (아직 외래키는 NULL인 상태)

poster의 ImgFIles를 List<UploadFile>로하여 poster를 DB에 저장하면

poster의 ImgFiles들의 외래 키를 설정해주는 UPDATE 쿼리가 날아간다.

 

구현 결과

DB 저장 결과

 

+ 파일 선택 없이 제출시 빈 파일이 들어가있다 => https://non-stop.tistory.com/528

Comments