옌의 로그

ENUM과 DB 매핑 전략 (feat. Enum Converter,, 코드 테이블,,) 본문

스터디/기타

ENUM과 DB 매핑 전략 (feat. Enum Converter,, 코드 테이블,,)

dev-yen 2025. 8. 28. 13:32

최근 프로젝트에서 enum을 자주 사용하게 되면서, 한 가지 고민이 생겼다.

 

  • 코드 상에서는 enum으로 깔끔하게 관리하고 싶은데
  • DB에는 그 값이 int로 저장된다면?

 

예를 들어, 상품 상태(ProductStatus)를 enum으로 선언하고 있지만 DB에는 0, 1, 2 같은 숫자로 저장하는 구조였다. 그럼 이 숫자들이 도대체 뭘 의미하는지 코드만 봐서는 알기 어렵다.

 

이를 해결하기 위해 사용했던 방법들을 정리하고, 코드 테이블 방식까지 비교해보려 한다.

 


 

1.  Enum의 순서(ORDINAL)를 그대로 사용하는 방식

가장 간단하고 자주 쓰이는 방법이다. enum 선언 순서가 곧 DB의 숫자 값이 된다.

@Getter
public enum ProductStatus {
    READY,       // 0
    IN_PROGRESS, // 1
    COMPLETE     // 2
}

 

Entity엔 이렇게 선언한다

@Enumerated(EnumType.ORDINAL)
private ProductStatus status;

장점

  • 코드가 매우 간단하다.
  • 별도의 매핑 로직이 필요 없다.

 

단점

  • 순서가 바뀌면 망한다.
  • 중간에 enum을 추가하거나 제거하면 기존 데이터 해석이 꼬인다.
  • 의미가 명확하지 않다. (0이 뭔 의미인지 알기 어려움)

 

개인적으로는 단기 프로젝트나 테스트용 DB라면 쓸 수도 있겠지만, 실서비스라면 비추천한다.

 

 


 

2.  EnumConverter를 활용한 자동 매핑

1. 공통 인터페이스 정의

public interface BaseEnum<T> {
    T getCode(); // DB에 저장되는 값
}

 

2. 실제 enum에 적용

@Getter
@RequiredArgsConstructor
public enum ProductStatus implements BaseEnum<Integer> {
    READY(0, "준비중"),
    IN_PROGRESS(1, "진행중"),
    COMPLETE(2, "완료됨");

    private final Integer code;
    private final String label;

    @JsonCreator // JSON → enum 역직렬화 시 사용
    public static ProductStatus of(Integer code) {
        return Arrays.stream(values())
                .filter(v -> v.getCode().equals(code))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("Invalid ProductStatus code: " + code));
    }
}

 

3. BaseEnum용 공통 Converter

public abstract class AbstractBaseEnumConverter<E extends Enum<E> & BaseEnum<T>, T> 
        implements AttributeConverter<E, T> {

    private final Class<E> enumClass;

    protected AbstractBaseEnumConverter(Class<E> enumClass) {
        this.enumClass = enumClass;
    }

    @Override
    public T convertToDatabaseColumn(E attribute) {
        return attribute != null ? attribute.getCode() : null;
    }

    @Override
    public E convertToEntityAttribute(T dbData) {
        if (dbData == null) return null;

        return Arrays.stream(enumClass.getEnumConstants())
                .filter(e -> dbData.equals(e.getCode()))
                .findFirst()
                .orElseThrow(() ->
                        new IllegalArgumentException("Unknown code [" + dbData + "] for enum " + enumClass.getSimpleName()));
    }
}

 

 

4. 실제 Enum용 Converter 구현

@Converter(autoApply = false) // 명시적으로 지정할 수 있게
public class ProductStatusConverter extends AbstractBaseEnumConverter<ProductStatus, Integer> {
    public ProductStatusConverter() {
        super(ProductStatus.class);
    }
}
Entity에서는 다음과 같이 사용:
@Convert(converter = ProductStatusConverter.class)
private ProductStatus productStatus;

 

 

이점 요약

항목설명

재사용성 BaseEnum + 공통 Converter로 모든 enum에 적용 가능
유지보수성 enum 값에 의미 부여 (label 등)와 안전한 역매핑
확장성 다른 타입 (String, Long 등)도 쉽게 확장 가능
안정성 잘못된 DB 값에 대한 방어 로직 포함 (orElseThrow)

 

 

 


 

 

 

3. (번외) 코드 테이블과 조인하여 관리하는 방식

Enum이 아니라 DB 테이블로 관리하는 방식도 있다.

id code name
1 PRODUCT_READY 준비중
2 PRODUCT_INPROG 진행중
3 PRODUCT_DONE 완료됨

이렇게 “코드 테이블”을 별도로 만들어놓고, FK로 조인하여 의미를 해석하게 만든다.

 

 

사용 예시

@Entity
public class Product {
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "status_id")
    private Code productStatus;

}
@Entity
public class Code {
    
    @Id
    private Long id;

    private String code;   // ex. "PRODUCT_READY"
    private String name;   // ex. "준비중"

}

 

장점

  • 코드값의 의미를 DB에서 명확하게 관리할 수 있다.
  • 코드 변경이 필요할 경우 enum을 수정하지 않아도 된다.
  • 운영/기획 팀에서 직접 코드 테이블을 관리 가능.

 

 단점

  • 단순 enum에 비해 구현과 조회가 번거롭다.
  • enum처럼 switch-case를 못 쓴다.
  • 코드값 오타에 대한 컴파일 타임 체크가 불가능

 

보통 코드값이 외부에 노출되거나, 관리 주체가 기획/운영인 경우 코드 테이블 방식을 많이 쓴다.

 

 

실제 서비스에서 어떻게 선택?

구분 enum ordinal  enum + converter  코드 테이블
장점 단순함 명확한 값 관리 유연한 외부 관리
단점 순서 변경 위험 구현 번거로움 조회 비용, 타입 안정성 낮음
추천 케이스 테스트, 임시 내부 로직 enum 외부 노출, 다국어 지원

 

 


 

마무리하며.  . .

처음에는 무조건 enum converter가 필요한 줄 알고 만들었었는데, 단순한 status 들의 경우 enum converter가 오히려 오버 스펙이 된단 걸 깨달은 어느 날. .  상태값이 크게 바뀔 일이 없는 것들의 경우 ordinal로 관리하는게 더 easy한 코드인 것 같다.

 

추가로, 코드 테이블은 아직 사용해본 적이 없어서 어떤 상황에서 유용하게 쓰일 수 있을 지 잘 모르겠다.
기회가 된 다면 써보고 싶다!

 

 

Comments