区分値とその名称の管理 Enum(Java) vs DB

システム開発をしていると、
「1:男、2:女」「A:優 B:良 C:可 D:否」というように、区分値とその意味を対応させたいことが多々あります。
また、区分値から文字列に変換したいケースも多いです。

そこで、今回はJavaのEnumを使った方法と、DBのマスタテーブルに保存する方法で、比較したいと思います。

言語はJava、DBはMySQLを使う想定です。

# 1. Enumクラス
Java5以降では、列挙型という概念があります。
Effective Javaでも定数として使うことを推奨されていますね。

まずは、以下のようなエンコーダーインタフェースを定義します。

```java:エンコーダーインタフェース
/**
 * Enum用エンコーダーインターフェース
 *
 * @param <T> Enum定義
 */
public interface Encodable<T extends Serializable> {
    /**
     * エンコーダー
     *
     * @return Enum定義
     */
    T getCode();

    /**
     * コードの名称を返す.
     *
     * @return 名称
     */
    String getName();
}
```

区分値から文字列を生成するために、デコーダークラスを以下のように定義します。


```java:デコーダークラス
/**
 * Enum用デコーダークラス
 *
 * @param <K> コード値
 * @param <V> バリュー
 */
public class Decoder<K extends Serializable, V extends Encodable<K>> {

    /** マップ定義 */
    private Map<K, V> map;

    /**
     * デコーダー
     *
     * @param values Enum群
     */
    private Decoder(V[] values) {
        map = new HashMap<K, V>(values.length);

        for (V value : values) {
            V old = map.put(value.getCode(), value);

            // コード値の重複はサポートしない
            if (old != null) {
                throw new IllegalArgumentException("duplicated code: " + value);
            }
        }
    }

    /**
     * デコーダー
     *
     * @param code コード値
     * @return Enumクラス
     */
    public V decode(K code) {
        return map.get(code);
    }

    /**
     * 型引数の指定を省略するための定義
     *
     * @param <L> コード値
     * @param <W> バリュー
     * @param values Enum群
     * @return デコーダー
     */
    public static <L extends Serializable, W extends Encodable<L>> Decoder<L, W> create(W[] values) {
        return new Decoder<L, W>(values);
    }
}
```

実際のクラスは以下のような形になります。
性別の場合以下のようになります。

```java:性別の列挙型クラス
/**
 * 性別の列挙型クラス.
 * 
 */
public enum Gender implements
                     Encodable<Integer> {

    /** 男 */
    MALE(1, "男"),
    /** 女 */
    FEMALE(2, "女");

    /** デコーダー */
    private static final Decoder<Integer, Gender> DECODER = Decoder.create(values());

    /** コード値 */
    private final Integer code;

    /** 名称 */
    private final String name;

    /**
     * コンストラクタ.
     * 
     * @param code コード値
     * @param name 名称
     */
    private Gender(Integer code, String name) {
        this.code = code;
        this.name = name;
    }

    @Override
    public Integer getCode() {
        return code;
    }

    /**
     * コード値からEnumクラスを取得する.
     * 
     * @param code コード値
     * @return 受領形式Enumクラス
     */
    public static Gender decode(Integer code) {
        return DECODER.decode(code);
    }

    /**
     * 名称を取得するメソッド.
     * 
     * @return 名称
     */
    public String getName() {
        return name;
    }
}
```

以下のように、decodeクラスとgetNameメソッドで、名称が取得できます。

```java:使用例
// 1:男性
Gender male = Gender.decode(1);
String name = male.getName();

```

## メリット
### 保守性が高い
プログラム内に記載し、意味のあるクラス名をつければ、可読性、保守性が高く、
保守がしやすいコードになります。

### ロジックの記載が容易
DBにいちいち接続しなくても、区分名称が取得できるため、処理としても楽です。

## デメリット
### 区分値の追加、名称の変更が容易でない
仕様変更などで、区分値の追加や、名称の変更がある際、プログラム修正が発生し、リリース作業も発生します。

## どんな時に使うか
性別、日本の都道府県など、まず変更されえないものをEnumで管理すると良いと思います。
変更される場合、ロジックの修正とかが必要になるケースも多いと思います。

# 2. DBのマスタテーブル

以下のようなマスタテーブルを使って、idとその名称を管理するような形になります。
名称以外にも付加情報を入れることももちろん可能です。

```MySQL:ログインユーザマスタ
CREATE TABLE login_user_mst (
    id VARCHAR(11) NOT NULL,
    password CHAR(40) NOT NULL comment 'ログインパスワード',
    name VARCHAR(255) NOT NULL comment '氏名',
    name_kana VARCHAR(255) default NULL comment '氏名カナ',

    PRIMARY KEY(id)
) CHARACTER SET 'utf8'
  COMMENT 'ログインユーザー';
```
データの例

|       id |password        |name             |name_kana       |
|---------:|---------------:|:---------------|:---------------|
| 10000001 | xxxxxxxxxxxxxx | 鈴木　奈々子    | スズキナナコ   |
| 10000002 | xxxxxxxxxxxxxx | 松本　かずお    | マツモトカズオ |
| 10000003 | xxxxxxxxxxxxxx | 渡邊　拓        | ワタナベタク   |



## メリット
### 名称の変更がしやすい
区分値の名称を変更するのは、DBのレコードを更新するだけで良いので、
プログラム修正が発生せずに変更が可能なので、コストがかなり少ないです。

### テーブル結合で区分名称を取得できる
マスタテーブルを結合して、SQLだけで名称を取得できるので、
SQLの結果を、画面で表示したりCSVに出力するだけ、というロジックだけで良くなります。

## デメリット
### 区分値による分岐処理の際は意味がない
例えば、区分値によって、処理の分岐が起こる場合、
その区分値をアプリ側に持たざるを得ないため、
DBに値を持っていても、ダブルメンテナンス状態になります。

### テーブルの数が増えると管理しにくい
「定数はすべてマスタテーブルで管理しよう」というルールがあるプロジェクトだと、
定数の種類が多くなると、定数ごとにテーブルを作っていると、膨大な数のマスタテーブルが出来上がります。
マスタテーブルであれば、"mst_"という接頭辞をつけるというようなルールがあれば、まだ管理しやすいかと思いますが。。

## どんな時に使うか
ログインユーザの管理や、営業拠点情報、
比較的変更されるケースが多めのものに使うと良いかと思います。

# 終わりに

区分値と名称の管理をどうするか、というのは永遠の課題だと考えています（個人的に）。
今のプロジェクトでは、Enum型、DBマスタの両方を使っています。
それぞれの特性を理解して使うべきかと思います。