システム開発をしていると、
「1:男、2:女」「A:優 B:良 C:可 D:否」というように、区分値とその意味を対応させたいことが多々あります。
また、区分値から文字列に変換したいケースも多いです。
そこで、今回はJavaのEnumを使った方法と、DBのマスタテーブルに保存する方法で、比較したいと思います。
言語はJava、DBはMySQLを使う想定です。
1. Enumクラス
Java5以降では、列挙型という概念があります。
Effective Javaでも定数として使うことを推奨されていますね。
まずは、以下のようなエンコーダーインタフェースを定義します。
/**
* Enum用エンコーダーインターフェース
*
* @param <T> Enum定義
*/
public interface Encodable<T extends Serializable> {
/**
* エンコーダー
*
* @return Enum定義
*/
T getCode();
/**
* コードの名称を返す.
*
* @return 名称
*/
String getName();
}
区分値から文字列を生成するために、デコーダークラスを以下のように定義します。
/**
* 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);
}
}
実際のクラスは以下のような形になります。
性別の場合以下のようになります。
/**
* 性別の列挙型クラス.
*
*/
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メソッドで、名称が取得できます。
// 1:男性
Gender male = Gender.decode(1);
String name = male.getName();
メリット
保守性が高い
プログラム内に記載し、意味のあるクラス名をつければ、可読性、保守性が高く、
保守がしやすいコードになります。
ロジックの記載が容易
DBにいちいち接続しなくても、区分名称が取得できるため、処理としても楽です。
デメリット
区分値の追加、名称の変更が容易でない
仕様変更などで、区分値の追加や、名称の変更がある際、プログラム修正が発生し、リリース作業も発生します。
どんな時に使うか
性別、日本の都道府県など、まず変更されえないものをEnumで管理すると良いと思います。
変更される場合、ロジックの修正とかが必要になるケースも多いと思います。
2. DBのマスタテーブル
以下のようなマスタテーブルを使って、idとその名称を管理するような形になります。
名称以外にも付加情報を入れることももちろん可能です。
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マスタの両方を使っています。
それぞれの特性を理解して使うべきかと思います。