티스토리 뷰
첨부파일 API(업로드/다운로드/삭제)는 만들어놨는데, 게시글 기능이랑 따로 놀고 있었다.
게시글을 지워도 파일은 안 지워지고, 글 쓸 때 파일을 붙이려면 API를 두 번 불러야 했다. 좀 이걸 개선해보려고 한다
- 게시글 삭제 시 파일이 안 지워지는 버그
files 테이블은 board_id를 ON DELETE CASCADE로 참조하고 있어서, 게시글을 지우면 DB의 files 행은 자동으로 같이 지워진다.
근데 이건 DB 얘기고 디스크에 있는 실제 파일은 CASCADE가 손댈 수 없는 영역이다.
BoardService.deleteBoard를 보니 boardRepository.deleteBoard(id)만 부르고 끝이었고, 그래서 게시글을 지울 때마다 디스크에 안 쓰는 파일이 계속 쌓이고 있었다.
// FileService.java
// 게시글 삭제 시 호출됨. 해당 게시글의 파일들을 디스크에서만 삭제 (DB는 CASCADE로 이미 처리됨)
public void deleteFilesFromDisk(Long boardId) throws IOException {
// 삭제되기 전에 파일 목록을 미리 조회해둠
List<FileEntity> files = fileRepository.selectFilesByBoardId(boardId);
// 목록을 돌면서 디스크에서 하나씩 삭제
for (FileEntity file : files) {
Path filePath = Paths.get(file.getFilePath()).resolve(file.getStoredName());
Files.deleteIfExists(filePath);
}
}
//
// BoardService.java
public void deleteBoard(Long id, String password) throws IOException {
BoardEntity board = boardRepository.selectBoardDetail(id);
if (!board.getPassword().equals(password)) {
throw new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
}
fileService.deleteFilesFromDisk(id); // 먼저: 디스크 파일 정리
boardRepository.deleteBoard(id); // 그다음: DB에서 게시글 삭제 (CASCADE)
}
중요한건 순서다.
boardRepository.deleteBoard(id)를 먼저 부르면 CASCADE가 files 행을 먼저 지워버려서, 그다음엔 이 게시글에 어떤 파일이 있었는지 알 방법이 없어진다. 그래서 디스크 정리를 먼저 하고, DB 삭제를 나중에 했다.
- 게시글 등록 시 파일을 같이 보내기
지금까지 JSON은 @RequestBody, 파일은 @RequestParam으로 완전히 다른 요청으로 받았다. 두번이나 요청을 받는 구조가 별로다. 이번엔 하나의 요청 안에 게시글 정보(JSON)랑 파일들을 같이 실어보았다
multipart는 요청을 여러 칸으로 나누는 형식이라고 했었는데, 그 칸 중 하나에 파일 대신 JSON 텍스트를 넣을 수도 있다. 이 칸을 객체로 변환해서 받는 어노테이션이 @RequestPart다. @RequestBody의 multipart 버전이라고 보면 된다.
// BoardController.java
@PostMapping("/api/boards")
public BoardResponse createBoard(
@RequestPart("request") @Valid BoardCreateRequest request, //JSON -> BoardCreateRequest
@RequestParam(value = "files", required = false) List<MultipartFile> files
) throws IOException {
return boardService.createBoard(request, files);
}
// BoardService.java
public BoardResponse createBoard(BoardCreateRequest request, List<MultipartFile> files) throws IOException {
BoardEntity board = boardMapper.toEntity(request);
board.setViewCount(0);
board.setCreatedAt(LocalDateTime.now());
boardRepository.insertBoard(board); // 이 순간 board.getId()에 생성된 id가 채워짐
if (files != null && !files.isEmpty()) {
fileService.uploadFiles(board.getId(), files);
}
return boardMapper.toResponse(board);
}
insertBoard로 id가 생기자마자 그 id로 기존 uploadFiles를 그대로 불렀다. 파일은 옵션이라 files가 없거나 비어있으면 그냥 건너뛴다
- 게시글 수정 시 파일 추가/삭제
요구사항은 첨부된 파일 삭제 가능, 취소하면 삭제되지 않음 이었는데, 여기서 갈림길이 두 개 있었다
- 수정 요청에 파일 정보를 어떻게 실을지? 지울 id + 새로 추가할 파일을 따로 받을지, 아니면 최종 파일 목록을 통째로 받아서 서버가 비교할지
- 저장해야만 반영을 누가 책임질지? 프론트가 저장 버튼 누르기 전엔 API를 아예 안 부르는 걸로 충분한지, 아니면 서버가 임시 변경사항을 들고 있어야 하는지
둘 다 후자가 더 정교하지만 훨씬 복잡하다. 시간 관계상 전자(라고 쓰고 쉬운 쪽)를 선택했다 (ㅋㅋ)
//BoardService.java
public BoardResponse updateBoard(Long id, BoardUpdateRequest request, List<MultipartFile> files) throws IOException {
BoardEntity board = boardRepository.selectBoardDetail(id);
if (!board.getPassword().equals(request.password())) {
throw new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
}
board.setCategoryId(request.categoryId());
board.setTitle(request.title());
board.setContent(request.content());
board.setUpdatedAt(LocalDateTime.now());
boardRepository.updateBoard(board);
// 지울 파일이 있으면 하나씩 삭제 (기존 deleteFile 재사용)
if (request.deleteFileIds() != null) {
for (Integer fileId : request.deleteFileIds()) {
fileService.deleteFile(fileId);
}
}
// 새로 추가할 파일이 있으면 업로드 (기존 uploadFiles 재사용)
if (files != null && !files.isEmpty()) {
fileService.uploadFiles(id, files);
}
return boardMapper.toResponse(board);
}
쉬운 쪽을 선택하니 새로 만든 로직이 거의 없었다. 파일 삭제 API 만들 때 이미 짰던 fileService.deleteFile(id), 업로드 API 만들 때 짰던 fileService.uploadFiles(id, files)를 그대로 갖다 썼다. 쉬운 설계가 재사용 가능한 설계랑 같은 의미인걸까
테스트
실제로 서버를 켜서 게시글 생성(파일 포함) → 파일 목록 조회 → 게시글 수정(기존 파일 삭제 + 새 파일 추가) → 파일 목록 재조회까지 전체 흐름을 확인했다. 지운 파일은 목록에서도, uploads/ 폴더에서도 사라졌고, 새로 추가한 파일만 남았다
엄청 복잡할 줄 알았는데 재사용 가능한 방향으로 가다보니 코드를 재활용해서 시간이 많이 소요되지는 않았다.
'게시판 만들기 > 4주차' 카테고리의 다른 글
| 만들자 게시판 4주차 (3) - 게시글 검색/페이징/첨부파일 표시 구현 (1) | 2026.07.24 |
|---|---|
| 만들자 게시판 4주차 (2) - 유효성 검증 추가 (1) | 2026.07.24 |
| 만들자 게시판 4주차 (1) - 첨부파일 삭제 API 구현 (0) | 2026.07.24 |
