파일 테이블 설계 정리


1. 핵심 질문: 통합 테이블 vs 분리 테이블

  통합 테이블 분리 테이블
구조 files 테이블 하나에 ref_type, ref_id 컬럼으로 구분 board_files, product_files 처럼 도메인별로 분리
장점 설계 단순, 개발 빠름 도메인별 특화 필드 관리, 성능 유리
단점 테이블 커지면 성능 저하, 특화 필드 넣기 어려움 비슷한 코드 중복 (상속으로 완화 가능)
적합한 상황 소규모 프로젝트, 파일 속성이 비슷할 때 대규모, 도메인별 파일 속성이 다를 때

2. 작은 프로젝트는 통합 테이블이 정답

아래 조건이면 고민 없이 통합 테이블로 가면 된다.

files 테이블
├── id
├── ref_type  (BOARD, PRODUCT, USER ...)
├── ref_id    (각 도메인의 PK)
├── original_file_name
├── stored_file_name
├── file_path
├── file_size
└── content_type

3. 테이블을 분리할 때의 기준 (신호 3가지)

① 도메인별 특화 필드가 생길 때

② 참조하는 상위 테이블 종류가 너무 많을 때

③ 첨부 테이블 용량이 너무 커질 때


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은 어떤 상황에서도 하나만 유지