JPA 연관 관계 설정하기

차윤하·2025년 4월 30일
0

파일 업로드 및 다운로드 기능을 구현하면서 게시글(Post)과 첨부파일(Attachment) 간의 연관관계를 설정하고, 파일 업로드까지 구현해봤다.
이번에 내가 배운 내용들을 한 번에 정리해두려고 한다.


Post와 Attachment 연관관계 설정

1. 관계 구조

  • 게시글 하나(Post)에 첨부파일 여러 개(Attachment)가 달릴 수 있으니까 1:N 관계로 매핑했다.
  • Post 쪽에서는 다음처럼 설정:
@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);
}
  • Post가 첨부파일을 관리하는 게 더 자연스러워 보였다.
  • 실수할 가능성도 줄어든다. 연관관계를 Post 쪽에서 확실히 책임지도록 만들어줌.

@PrePersist로 자동 날짜 저장

매번 createdAt 필드에 현재 시간을 넣는 게 귀찮아서, JPA 라이프사이클 콜백을 이용했다.

@PrePersist
public void prePersist() {
    this.createdAt = LocalDateTime.now();
}

이제는 저장 전에 자동으로 날짜가 들어가니까 깔끔하다.


파일 업로드 처리 흐름

FileList 개념

프론트에서 <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            // 파일 개수

FormData로 전송

일반 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
) {
    // 저장 로직
}

Axios로 전송

await axios.post('http://localhost:8080/file', formData, {
    headers: {
        "Content-Type": "multipart/form-data",
    },
});

Content-Type은 생략해도 브라우저가 자동으로 설정해주긴 하지만, 명시적으로 넣는 게 확실하다.


정리

이번에 연관관계 매핑 + 파일 업로드까지 흐름을 정리하면서 느낀 점:

  • JPA에서 연관관계 설정은 생각보다 실수가 많을 수 있어서, 처음부터 편의 메서드로 안정성 높이는 게 중요하다.
  • cascade, orphanRemoval 덕분에 저장/삭제가 훨씬 간단해진다.
  • FormData와 multipart/form-data 구조는 한 번 익혀두면, 이미지 업로드, 파일 첨부 등에서 자주 쓴다.

시간 들여 구현하고 나니까 훨씬 구조가 깔끔해진 느낌. 다음엔 파일 다운로드나 미리보기 기능도 붙여볼 예정이다.

profile
풀스택 개발자가 되고 싶은 꿈나무

0개의 댓글