파일 업로드 및 다운로드 기능을 구현하면서 게시글(Post)과 첨부파일(Attachment) 간의 연관관계를 설정하고, 파일 업로드까지 구현해봤다.
이번에 내가 배운 내용들을 한 번에 정리해두려고 한다.
Post
)에 첨부파일 여러 개(Attachment
)가 달릴 수 있으니까 1:N 관계로 매핑했다.@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Attachment> attachments = new ArrayList<>();
mappedBy = "post"
: Attachment
안의 Post post
필드 기준으로 연관관계 매핑cascade = CascadeType.ALL
: 게시글 저장 시 첨부파일도 함께 저장orphanRemoval = true
: 게시글에서 첨부파일을 제거하면 DB에서도 자동 삭제new ArrayList<>()
: 리스트는 NullPointException 방지를 위해 항상 초기화양방향 연관관계일 경우, 양쪽에 모두 값을 설정해줘야 정확한 관계가 만들어진다. 이걸 깜빡하면 매핑이 꼬일 수도 있다. 그래서 아래처럼 편의 메서드를 만들어 사용하는 게 안정적이다.
public void addAttachment(Attachment attachment) {
attachments.add(attachment);
attachment.setPost(this);
}
매번 createdAt
필드에 현재 시간을 넣는 게 귀찮아서, JPA 라이프사이클 콜백을 이용했다.
@PrePersist
public void prePersist() {
this.createdAt = LocalDateTime.now();
}
이제는 저장 전에 자동으로 날짜가 들어가니까 깔끔하다.
프론트에서 <input type="file" multiple>
을 사용하면 여러 개 파일을 선택할 수 있는데, 이때 나오는 건 배열이 아니라 FileList
객체다. 배열처럼 생겼지만 진짜 배열은 아니다.
e.target.files[0].name // 파일 이름
e.target.files[0].size // 파일 크기 (byte 단위)
e.target.files[0].type // MIME 타입
e.target.files.length // 파일 개수
일반 JSON은 텍스트만 전송할 수 있어서, 파일 전송은 multipart/form-data 형식을 사용해야 한다. 이럴 때 FormData
객체를 사용하면 편하다.
const formData = new FormData();
formData.append("title", newPost.title);
formData.append("content", newPost.content);
for (let i = 0; i < selectedFile.length; i++) {
formData.append("files", selectedFile[i]);
}
서버 쪽에서는 @RequestParam
이나 List<MultipartFile>
로 받을 수 있다.
@PostMapping("/file")
public ResponseEntity<?> upload(
@RequestParam String title,
@RequestParam String content,
@RequestParam List<MultipartFile> files
) {
// 저장 로직
}
await axios.post('http://localhost:8080/file', formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
Content-Type
은 생략해도 브라우저가 자동으로 설정해주긴 하지만, 명시적으로 넣는 게 확실하다.
이번에 연관관계 매핑 + 파일 업로드까지 흐름을 정리하면서 느낀 점:
시간 들여 구현하고 나니까 훨씬 구조가 깔끔해진 느낌. 다음엔 파일 다운로드나 미리보기 기능도 붙여볼 예정이다.