概要

Enum を DB カラムや JSON フィールドに保存する場面は業務システムで頻繁に発生します。このとき ordinal() をそのまま整数カラムに入れるコードを見かけることがありますが、これは Enum の定義順を変更しただけで既存データが壊れるという重大なリスクを抱えています。name() による保存は ordinal よりは安全ですが、要素名のリファクタリングで非互換が生じます。もっとも堅牢なのは、Enum に明示的なコード値フィールドを持たせ、そのコード値で保存・復元する方法です。この記事では、ordinal がなぜ危険なのかを具体例で示したうえで、明示的コード値 + fromDbCode メソッドによる永続化パターンを実装します。Java 標準のシリアライズにおける Enum のシングルトン保証についても触れ、DB 保存・JSON 変換・Java シリアライズそれぞれの注意点を整理します。

使いどころ

注文ステータスを DB の VARCHAR カラムに保存する際、ordinal ではなく明示的なコード値(PEND, PROC 等)を使って安全に永続化する

REST API のレスポンス JSON に Enum のコード値を含める際、name() ではなく業務で定義されたコード値を返すよう設計する

既存テーブルの数値コードカラムと Enum を対応づける fromDbCode メソッドを実装し、DAO 層での変換を一元化する

コード例

EnumSerializeExample.java
import java.util.Arrays;

public class EnumSerializeExample {

    // 明示的なコード値で永続化する Enum
    enum OrderStatus {
        PENDING("PEND"),
        PROCESSING("PROC"),
        COMPLETED("COMP"),
        CANCELLED("CANC");

        private final String dbCode;

        OrderStatus(String dbCode) {
            this.dbCode = dbCode;
        }

        public String getDbCode() { return dbCode; }

        // コード値から Enum に復元
        public static OrderStatus fromDbCode(String code) {
            return Arrays.stream(values())
                .filter(s -> s.dbCode.equals(code))
                .findFirst()
                .orElseThrow(() ->
                    new IllegalArgumentException("不明なコード: " + code));
        }
    }

    // record でデータクラスを定義(Java 16+)
    record Order(String orderId, OrderStatus status) {
        @Override
        public String toString() {
            return "Order{id='" + orderId + "', status="
                + status + "('" + status.getDbCode() + "')}";
        }
    }

    public static void main(String[] args) {
        // DB 保存イメージ: Enum → コード値
        var status = OrderStatus.PROCESSING;
        var dbValue = status.getDbCode(); // "PROC"
        System.out.println("DB保存値: " + dbValue);

        // DB 読込イメージ: コード値 → Enum
        var restored = OrderStatus.fromDbCode(dbValue);
        System.out.println("DB復元: " + restored);

        // name() による保存(ordinal より安全だがリネームに弱い)
        var nameValue = status.name(); // "PROCESSING"
        var byName = OrderStatus.valueOf(nameValue);
        System.out.println("name() → valueOf(): " + byName);

        // ordinal の危険性: 定義順を変えると壊れる
        for (var s : OrderStatus.values()) {
            System.out.println(s.name()
                + " ordinal=" + s.ordinal()
                + " dbCode=" + s.getDbCode());
        }

        // record と組み合わせた使用例
        var order = new Order("ORD-001", OrderStatus.COMPLETED);
        System.out.println(order);
    }
}

Java 8 / 17 / 21 の完全なサンプルコードは GitHub リポジトリ で確認できます。

Version Coverage

fromDbCode を Arrays.stream で簡潔に書ける。record を使って Order を定義すれば、equals/hashCode/toString が自動生成される。

Java 17
// Java 17: Stream + record で簡潔に
public static OrderStatus fromDbCode(String code) {
    return Arrays.stream(values())
        .filter(s -> s.dbCode.equals(code))
        .findFirst()
        .orElseThrow(() ->
            new IllegalArgumentException("不明なコード: " + code));
}
record Order(String orderId, OrderStatus status)
    implements Serializable { }

Library Comparison

標準 Enum(明示的コード値)DB カラムや JSON に保存する値を完全に制御したいとき。外部ライブラリ不要で最も安全。逆引きメソッドを自前で書く必要がある。要素が多い場合は static Map でインデックスを作る工夫が要る。
Jackson @JsonValue / @JsonCreatorJSON 変換を Jackson に任せている場合に、Enum のシリアライズ形式をアノテーションで指定したいとき。Jackson 依存が前提。純粋な JDBC 保存には適用されないため、DB 永続化は別途対応が必要。
JPA @EnumeratedJPA エンティティで Enum カラムを扱うとき。EnumType.STRING なら name() で保存される。EnumType.ORDINAL はデフォルトで危険。STRING でも要素リネームに弱い。明示的コード値を使う場合は AttributeConverter が必要。

注意点

ordinal() は定義順に連動するため、要素の挿入・並び替えで過去データとの対応が壊れる。本番データが入った後では修正コストが非常に大きい

name() による保存は要素名の変更(リネーム)で非互換になる。ただし ordinal よりは安全なので、コード値設計の余裕がない場合は name() を選ぶ

fromDbCode で一致しないコードが来た場合は IllegalArgumentException を投げるのが基本。null や空文字の入力も考慮すること

Java 標準のシリアライズ(ObjectOutputStream)では Enum はシングルトンが保証されるが、Enum フィールドの追加・削除は serialVersionUID に影響しない点に注意

FAQ

name() と明示的コード値のどちらで保存すべきですか。

明示的コード値が最も安全です。name() はリファクタリングに弱く、ordinal は定義順変更に弱い。業務で使うコード体系がすでにある場合はそれを Enum に持たせてください。

Enum をデシリアライズしたとき == で比較しても大丈夫ですか。

Java 標準シリアライズでは Enum のシングルトンが保証されるため、デシリアライズ後も == で正しく比較できます。ただし JSON からの変換は valueOf を経由するので同様に安全です。

既存の ordinal 保存をコード値保存に移行するにはどうすればよいですか。

移行バッチで既存データの ordinal をコード値に変換し、カラム型を変更します。Enum の定義順を絶対に変えないまま移行を完了させてください。移行完了まで定義順の凍結が必要です。

関連書籍

この記事のテーマをさらに深く学びたい方へ。

※ Amazon アソシエイトリンクを含みます