파일 테이블 설계 정리
1. 핵심 질문: 통합 테이블 vs 분리 테이블
| 통합 테이블 | 분리 테이블 | |
|---|---|---|
| 구조 | files 테이블 하나에 ref_type, ref_id 컬럼으로 구분 |
board_files, product_files 처럼 도메인별로 분리 |
| 장점 | 설계 단순, 개발 빠름 | 도메인별 특화 필드 관리, 성능 유리 |
| 단점 | 테이블 커지면 성능 저하, 특화 필드 넣기 어려움 | 비슷한 코드 중복 (상속으로 완화 가능) |
| 적합한 상황 | 소규모 프로젝트, 파일 속성이 비슷할 때 | 대규모, 도메인별 파일 속성이 다를 때 |
2. 작은 프로젝트는 통합 테이블이 정답
아래 조건이면 고민 없이 통합 테이블로 가면 된다.
- 파일의 종류가 2~3개 이하
- 파일마다 저장하는 정보가 90% 이상 일치
- 프로토타입/토이 프로젝트
files 테이블
├── id
├── ref_type (BOARD, PRODUCT, USER ...)
├── ref_id (각 도메인의 PK)
├── original_file_name
├── stored_file_name
├── file_path
├── file_size
└── content_type
3. 테이블을 분리할 때의 기준 (신호 3가지)
① 도메인별 특화 필드가 생길 때
- 이미지 → 가로/세로 해상도 필요
- 영상 → 재생 시간 필요
- 문서 → 페이지 수 필요
- 통합 테이블에 억지로 넣으면 → NULL 컬럼 지옥
② 참조하는 상위 테이블 종류가 너무 많을 때
ref_type에 게시판, 상품, 이벤트, 상담, 회원, 배너… 10개 이상- FK 제약 걸기 어렵고 데이터 정합성 관리 복잡해짐
③ 첨부 테이블 용량이 너무 커질 때
- 수천만 건 이상 → 인덱스 크기 증가 → 조회 느려짐
- 특정 도메인 데이터만 따로 삭제/백업하기 어려움
4. FileUtil은 항상 하나만 (공통 기술)
FileUtil = 기술적 기능 (물리 I/O)
- 파일을 디스크에 저장
- UUID 파일명 생성
- 확장자 체크, 용량 제한
비즈니스 로직이 아닌 기술 기능이므로
A 게시판이든 B 상품이든 동일하게 작동 → 무조건 하나만
5. 분리 테이블일 때 코드 구조
전략 패턴 or 상속 방식으로 코드 중복을 최소화한다.
FileUtil (공통 기술 - 물리 파일 저장)
↓
BoardFileService ← BOARD_FILES 테이블 전담
ProductFileService ← PRODUCT_FILES 테이블 전담
↓
BoardFacade / ProductFacade (시나리오 조립)
// BoardFacade 예시
@Transactional
public void writeBoard(BoardDto dto, MultipartFile file) {
String storedName = fileUtil.save(file); // 공통 - 물리 저장
Board board = boardService.save(dto); // 게시글 저장
boardFileService.save(board, storedName); // BOARD_FILES에 기록
}
// ProductFacade 예시
@Transactional
public void registerProduct(ProductDto dto, MultipartFile file) {
String storedName = fileUtil.save(file); // 공통 - 같은 FileUtil 사용
Product product = productService.save(dto); // 상품 저장
productFileService.save(product, storedName); // PRODUCT_FILES에 기록
}
6. JPA 상속 전략 (분리 테이블 + 공통 필드 공유)
도메인별 테이블을 나누되 공통 필드는 한 번만 작성하고 싶다면 @MappedSuperclass 사용.
@MappedSuperclass // DB 테이블은 따로, 필드만 공유
public abstract class BaseFile {
private String originalFileName;
private String storedFileName;
private String filePath;
private Long fileSize;
}
@Entity
@Table(name = "board_files")
public class BoardFile extends BaseFile {
@Id @GeneratedValue
private Long id;
@ManyToOne
private Board board;
}
@Entity
@Table(name = "product_files")
public class ProductFile extends BaseFile {
@Id @GeneratedValue
private Long id;
private Integer width; // 상품 이미지 전용 특화 필드
private Integer height;
@ManyToOne
private Product product;
}
7. 결론 요약
조그만 프로젝트 → 통합 테이블 (ref_type + ref_id 방식)
규모가 커지거나 아래 신호가 오면 → 분리 테이블 고려
1. 도메인별 특화 필드 필요
2. 참조 대상 테이블 종류가 너무 많음
3. 첨부 테이블 데이터가 수천만 건 이상
FileUtil은 어떤 상황에서도 하나만 유지