ENUM으로 제한된 값과 데이터 무결성 다루기

열거형 ENUM의 언어·데이터베이스 구현 방식과 상태, 권한, 설정 관리에서의 설계 판단 기준을 정리한다.

2026-08-14 · 최초 발행 2025-08-10

제한된 값 집합을 타입으로 드러내는 방법

ENUM(Enumeration)은 미리 정의한 제한된 값의 집합을 표현하는 열거형이다. 프로그래밍 언어와 데이터베이스에서 특정 값만 입력되도록 제어할 때 쓰며, 값의 의미를 이름으로 표현해 코드 가독성과 유지보수성을 높인다.

허용하지 않은 값의 유입을 막아 데이터 무결성을 지키고, 타입 안전성을 제공한다는 점도 핵심이다. 데이터베이스에서는 저장 공간과 인덱싱 측면의 효율을 기대할 수 있다.

언어별 열거형 표현

Java 5부터 열거형은 클래스의 특별한 형태로 제공된다. 단순한 상수 목록에 그치지 않고 생성자, 필드, 메소드를 함께 둘 수 있다.

// 기본 ENUM 선언
enum Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}

// 메소드와 생성자를 포함한 복잡한 ENUM
enum Planet {
    MERCURY(3.303e+23, 2.4397e6),
    VENUS(4.869e+24, 6.0518e6),
    EARTH(5.976e+24, 6.37814e6);

    private final double mass;   // 질량(kg)
    private final double radius; // 반지름(m)

    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }

    public double getMass() { return mass; }
    public double getRadius() { return radius; }
}

Java의 ENUM은 컴파일 시점 타입 검사가 가능하며, 각 열거 상수는 하나의 인스턴스만 존재한다. 필드와 메소드를 추가할 수 있고 switch 문에서도 효과적으로 사용할 수 있다.

C/C++의 열거형은 정수형 상수 집합에 가깝다.

// C/C++에서의 기본 ENUM
enum Color { RED, GREEN, BLUE };  // 기본적으로 RED=0, GREEN=1, BLUE=2 할당

// 명시적 값 할당
enum Status {
    ERROR = -1,
    SUCCESS = 0,
    PENDING = 1
};

C++11에서는 이름 범위를 분명히 하는 enum class가 도입됐다.

enum class Direction { NORTH, EAST, SOUTH, WEST };
// 사용: Direction::NORTH

데이터베이스 스키마에서 ENUM 사용하기

MySQL은 ENUM 타입을 직접 지원한다. 셔츠 크기처럼 선택지가 고정된 열을 선언할 수 있다.

CREATE TABLE shirts (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    size ENUM('XS', 'S', 'M', 'L', 'XL')
);

-- 데이터 삽입
INSERT INTO shirts VALUES (1, 'Blue Shirt', 'M');

MySQL ENUM은 내부적으로 정수값으로 저장하며, 첫 번째 요소는 1, 두 번째 요소는 2가 된다. 최대 65,535개의 요소를 정의할 수 있고 1-2 바이트만 사용한다. 입력 데이터 검증도 자동화된다.

PostgreSQL에서는 사용자 정의 타입으로 열거형을 만든 뒤 테이블 열에 적용한다.

-- ENUM 타입 생성
CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');

-- 테이블에 적용
CREATE TABLE person (
    name VARCHAR(100),
    current_mood mood
);

-- 데이터 삽입
INSERT INTO person VALUES ('John', 'happy');

상태·권한·설정에 적용하는 패턴

주문이나 사용자 계정처럼 정해진 상태값만 가져야 하는 도메인은 ENUM과 잘 맞는다.

enum OrderStatus {
    CREATED,
    PAYMENT_PENDING,
    PAID,
    SHIPPED,
    DELIVERED,
    CANCELLED,
    REFUNDED
}

데이터베이스에서도 같은 상태 집합을 열 정의에 반영할 수 있다.

CREATE TABLE orders (
    order_id INT PRIMARY KEY,
    customer_id INT,
    order_date DATETIME,
    status ENUM('CREATED', 'PAYMENT_PENDING', 'PAID', 'SHIPPED', 'DELIVERED', 'CANCELLED', 'REFUNDED')
);

접근 권한 역시 역할을 제한된 집합으로 관리하는 대표적인 대상이다.

enum UserRole {
    ADMIN,
    MANAGER,
    EDITOR,
    USER,
    GUEST
}

사용자 또는 시스템 설정에서 가능한 옵션을 한정할 때도 같은 방식을 적용할 수 있다.

CREATE TABLE user_preferences (
    user_id INT PRIMARY KEY,
    theme ENUM('LIGHT', 'DARK', 'SYSTEM'),
    notification_level ENUM('NONE', 'IMPORTANT', 'ALL')
);

ENUM은 정의한 값만 허용해 잘못된 입력을 막고, 매직 넘버 대신 의미 있는 이름을 사용하게 한다. 값 변경 지점을 한곳으로 모아 코드 중복을 줄이는 효과도 있다.

반면 데이터베이스의 ENUM 값을 추가하거나 변경하려면 ALTER TABLE 작업이 필요하며, 대규모 데이터베이스에서는 성능 이슈가 생길 수 있다. 모든 DBMS가 이를 지원하는 것도 아니고, MySQL ENUM은 다른 데이터베이스로 마이그레이션할 때 문제가 될 수 있다. 새 값이 자주 추가되거나 동적으로 할당돼야 한다면 확장성도 제한된다.

값이 변하는 도메인에서는 참조 테이블을 검토한다

값의 변경 가능성이 낮다면 ENUM이 적합하다. 반대로 값이 자주 바뀐다면 코드나 스키마 변경이 반복되므로 참조 테이블 같은 대안을 검토해야 한다.

낮음높음ENUM 설계 시작 변경 가능성?ENUM 사용 적합대안 고려: 참조 테이블 사용ENUM 구현참조 테이블 설계

다국어 표시명이 필요할 때도 참조 테이블은 코드값과 언어별 이름을 분리해 관리할 수 있다.

-- ENUM 대신 참조 테이블 사용
CREATE TABLE order_statuses (
    status_id INT PRIMARY KEY,
    status_code VARCHAR(20) UNIQUE,  -- 프로그램 내부 코드
    status_name_en VARCHAR(50),      -- 영어 표시명
    status_name_ko VARCHAR(50),      -- 한국어 표시명
    status_name_ja VARCHAR(50)       -- 일본어 표시명
);

CREATE TABLE orders (
    order_id INT PRIMARY KEY,
    status_id INT REFERENCES order_statuses(status_id)
);

열거형에 비즈니스 로직을 넣어 관련 동작을 함께 관리할 수도 있다.

enum PaymentMethod {
    CREDIT_CARD {
        @Override
        public double calculateFee(double amount) {
            return amount * 0.03; // 3% 수수료
        }
    },
    BANK_TRANSFER {
        @Override
        public double calculateFee(double amount) {
            return amount * 0.01; // 1% 수수료
        }
    },
    PAYPAL {
        @Override
        public double calculateFee(double amount) {
            return amount * 0.035; // 3.5% 수수료
        }
    };

    public abstract double calculateFee(double amount);
}

ENUM을 대신할 수 있는 모델

룩업 테이블은 변경 가능성이 높거나 다국어 지원이 필요한 경우에 유용하다. 값의 추가와 변경을 동적으로 처리할 수 있고, 상태별 메타데이터도 저장할 수 있다.

-- 상태 룩업 테이블
CREATE TABLE status_types (
    status_id INT PRIMARY KEY,
    status_code VARCHAR(20) UNIQUE,
    status_name VARCHAR(50),
    is_active BOOLEAN DEFAULT TRUE
);

-- 메인 테이블에서 참조
CREATE TABLE orders (
    order_id INT PRIMARY KEY,
    customer_id INT,
    status_id INT REFERENCES status_types(status_id)
);

Java에서는 ENUM이 도입되기 전 상수 클래스를 사용하는 방식도 널리 쓰였다.

public class OrderStatus {
    public static final int CREATED = 1;
    public static final int PAYMENT_PENDING = 2;
    public static final int PAID = 3;
    // ...

    // 인스턴스화 방지
    private OrderStatus() {}
}

여러 옵션을 동시에 조합해야 한다면 비트 플래그가 적합하다.

public class Permissions {
    public static final int NONE = 0;           // 0000
    public static final int READ = 1;           // 0001
    public static final int WRITE = 2;          // 0010
    public static final int EXECUTE = 4;        // 0100
    public static final int ALL = 7;            // 0111

    // 사용 예: READ와 WRITE 권한 조합
    int userPermission = READ | WRITE;          // 0011

    // 권한 체크
    boolean canRead = (userPermission & READ) != 0;
}

ENUM을 선택할 때는 값 집합의 안정성, 스키마 변경 부담, 이식성, 장기 유지보수 요구를 함께 봐야 한다. 값이 고정된 영역에서는 강한 제약과 명확한 표현을 제공하지만, 변화와 확장이 본질인 영역에서는 참조 테이블이 더 자연스러운 모델이 될 수 있다.

열거형ENUM데이터 무결성타입 안전성데이터베이스