web-dev-qa-db-ja.com

データベースに列挙型を保存する方法

列挙型をデータベースに保存する最良の方法は何ですか?

Javaはname()およびvalueOf()メソッドを提供して、enum値をStringに変換したり戻したりすることができます。これらの値は?

列挙型を一意の番号にする賢明な方法はありますか(ordinal()は安全ではありません)?

更新:

素晴らしくて速い答えをありがとう!思ったとおりでした。

ただし、「ツールキット」への注意。それが一つの方法です。問題は、作成する各Enumタイプに同じメソッドを追加する必要があることです。それは多くの重複したコードであり、現時点ではJavaはこのためのソリューションをサポートしていません(Java enumは他のクラスを拡張できません)。

111
user20298

neverは、列挙を数値の序数値として保存します。デバッグとサポートが非常に困難になります。実際の列挙値を文字列に変換して保存します。

public enum Suit { Spade, Heart, Diamond, Club }

Suit theSuit = Suit.Heart;

szQuery = "INSERT INTO Customers (Name, Suit) " +
          "VALUES ('Ian Boyd', %s)".format(theSuit.name());

そして、次のように読み返します。

Suit theSuit = Suit.valueOf(reader["Suit"]);

問題は、過去にEnterprise Managerを凝視し、解読しようとしていました。

Name                Suit
==================  ==========
Shelby Jackson      2
Ian Boyd            1

Name                Suit
==================  ==========
Shelby Jackson      Diamond
Ian Boyd            Heart

後者の方がはるかに簡単です。前者は、ソースコードを取得し、列挙メンバーに割り当てられた数値を見つける必要がありました。

はい、より多くのスペースが必要になりますが、列挙メンバー名は短く、ハードドライブは安価であり、問​​題が発生した場合に役立ちます。

さらに、数値を使用する場合は、それらに関連付けられます。古い数値を強制することなく、メンバーを適切に挿入または再配置することはできません。たとえば、Suit列挙を次のように変更します。

public enum Suit { Unknown, Heart, Club, Diamond, Spade }

になる必要があります:

public enum Suit { 
      Unknown = 4,
      Heart = 1,
      Club = 3,
      Diamond = 2,
      Spade = 0 }

データベースに保存されているレガシー数値を維持するため。

データベースでそれらをソートする方法

質問が浮上します:値を注文したいとしましょう。一部の人々は、列挙型の序数値でそれらをソートしたい場合があります。もちろん、列挙の数値によるカードの順序付けは無意味です。

SELECT Suit FROM Cards
ORDER BY SuitID; --where SuitID is integer value(4,1,3,2,0)

Suit
------
Spade
Heart
Diamond
Club
Unknown

それは私たちが望む順序ではありません-それらを列挙順にしたいのです:

SELECT Suit FROM Cards
ORDER BY CASE SuitID OF
    WHEN 4 THEN 0 --Unknown first
    WHEN 1 THEN 1 --Heart
    WHEN 3 THEN 2 --Club
    WHEN 2 THEN 3 --Diamond
    WHEN 0 THEN 4 --Spade
    ELSE 999 END

文字列を保存する場合、整数値を保存する場合と同じ作業が必要です。

SELECT Suit FROM Cards
ORDER BY Suit; --where Suit is an enum name

Suit
-------
Club
Diamond
Heart
Spade
Unknown

しかし、それは私たちが望む順序ではありません-それらを列挙順にしたいのです:

SELECT Suit FROM Cards
ORDER BY CASE Suit OF
    WHEN 'Unknown' THEN 0
    WHEN 'Heart'   THEN 1
    WHEN 'Club'    THEN 2
    WHEN 'Diamond' THEN 3
    WHEN 'Space'   THEN 4
    ELSE 999 END

私の意見では、この種のランキングはユーザーインターフェイスに属します。列挙値に基づいてアイテムを並べ替える場合:何か間違ったことをしています。

しかし、本当にそれをしたい場合は、Suitsディメンションテーブルを作成します。

| Suit       | SuitID       | Rank          | Color  |
|------------|--------------|---------------|--------|
| Unknown    | 4            | 0             | NULL   |
| Heart      | 1            | 1             | Red    |
| Club       | 3            | 2             | Black  |
| Diamond    | 2            | 3             | Red    |
| Spade      | 0            | 4             | Black  |

このように、使用するカードを変更する場合Kissing KingsNew Deck Order表示目的のために変更することができますすべてのデータを破棄する:

| Suit       | SuitID       | Rank          | Color  | CardOrder |
|------------|--------------|---------------|--------|-----------|
| Unknown    | 4            | 0             | NULL   | NULL      |
| Spade      | 0            | 1             | Black  | 1         |
| Diamond    | 2            | 2             | Red    | 1         |
| Club       | 3            | 3             | Black  | -1        |
| Heart      | 1            | 4             | Red    | -1        |

現在、内部プログラミングの詳細(列挙名、列挙値)をユーザー向けの表示設定で分離しています。

SELECT Cards.Suit 
FROM Cards
   INNER JOIN Suits ON Cards.Suit = Suits.Suit
ORDER BY Suits.Rank, 
   Card.Rank*Suits.CardOrder
153
Ian Boyd

それを避けるための特定のパフォーマンス上の理由がない限り、列挙に別のテーブルを使用することをお勧めします。追加のルックアップが本当にあなたを殺さない限り、外部キーの整合性を使用します。

スーツテーブル:

suit_id suit_name
1       Clubs
2       Hearts
3       Spades
4       Diamonds

選手テーブル

player_name suit_id
Ian Boyd           4
Shelby Lake        2
  1. 列挙を振る舞い(優先順位など)を持つクラスにリファクタリングする場合、データベースは既に正しくモデル化しています
  2. スキーマが正規化されているため、DBAは満足しています(文字列全体ではなく、プレーヤーごとに1つの整数を格納します。文字列にはタイプミスがある場合とない場合があります)。
  3. データベース値(suit_id)は列挙値から独立しているため、他の言語からのデータでも作業できます。
37
Tom

ここでの唯一の安全なメカニズムは、String name()値を使用することです。 DBに書き込むときは、could sprocを使用して値を挿入し、読み取るときにViewを使用します。このように、enumが変更された場合、sproc/viewに間接レベルがあり、これをDBに「課す」ことなく、enum値としてデータを表示できます。

5
oxbow_lakes

あなたが言うように、序数は少し危険です。例について考えてみましょう:

public enum Boolean {
    TRUE, FALSE
}

public class BooleanTest {
    @Test
    public void testEnum() {
        assertEquals(0, Boolean.TRUE.ordinal());
        assertEquals(1, Boolean.FALSE.ordinal());
    }
}

これを序数として保存した場合、次のような行があるかもしれません。

> SELECT STATEMENT, TRUTH FROM CALL_MY_BLUFF

"Alice is a boy"      1
"Graham is a boy"     0

しかし、ブール値を更新するとどうなりますか?

public enum Boolean {
    TRUE, FILE_NOT_FOUND, FALSE
}

これは、すべての嘘が「ファイルが見つかりません」と誤解されることを意味します

文字列表現を使用する方が良い

5
toolkit

大規模なデータベースの場合、数値表現のサイズと速度の利点を失うことには消極的です。多くの場合、Enumを表すデータベーステーブルになります。

外部キーを宣言することにより、データベースの一貫性を強化できます。ただし、場合によっては、外部キー制約として宣言しない方がよい場合があります。これにより、すべてのトランザクションにコストがかかります。以下を使用して、選択したときに定期的にチェックを行うことにより、一貫性を確保できます。

SELECT reftable.* FROM reftable
  LEFT JOIN enumtable ON reftable.enum_ref_id = enumtable.enum_id
WHERE enumtable.enum_id IS NULL;

このソリューションの残りの半分は、Java列挙型とデータベース列挙型テーブルの内容が同じであることを確認するテストコードを作成することです。これは読者の課題として残しておきます。

4
Roger Hayes

列挙名自体を保存するだけです-より読みやすくなります。

限られた値のセットがある列挙型に特定の値を保存することで混乱しました、例えば、charを使用して表現する限られたステータスのセットを持つこの列挙型(数値よりも意味があります):

public enum EmailStatus {
    EMAIL_NEW('N'), EMAIL_SENT('S'), EMAIL_FAILED('F'), EMAIL_SKIPPED('K'), UNDEFINED('-');

    private char dbChar = '-';

    EmailStatus(char statusChar) {
        this.dbChar = statusChar;
    }

    public char statusChar() {
        return dbChar;
    }

    public static EmailStatus getFromStatusChar(char statusChar) {
        switch (statusChar) {
        case 'N':
            return EMAIL_NEW;
        case 'S':
            return EMAIL_SENT;
        case 'F':
            return EMAIL_FAILED;
        case 'K':
            return EMAIL_SKIPPED;
        default:
            return UNDEFINED;
        }
    }
}

また、多くの値がある場合、enum内にMapを用意して、getFromXYZメソッドを小さく保つ必要があります。

3
JeeBee

データベースに列挙型を文字列として保存する場合、任意の列挙型を(デ)シリアル化するユーティリティメソッドを作成できます。

   public static String getSerializedForm(Enum<?> enumVal) {
        String name = enumVal.name();
        // possibly quote value?
        return name;
    }

    public static <E extends Enum<E>> E deserialize(Class<E> enumType, String dbVal) {
        // possibly handle unknown values, below throws IllegalArgEx
        return Enum.valueOf(enumType, dbVal.trim());
    }

    // Sample use:
    String dbVal = getSerializedForm(Suit.SPADE);
    // save dbVal to db in larger insert/update ...
    Suit suit = deserialize(Suit.class, dbVal);
2
Dov Wasserman

私の目的は、順序値ではなくデータベースに列挙文字列値を保持することであるという同じ問題に直面しています。

この問題を克服するために、@Enumerated(EnumType.STRING)を使用しましたが、私の目的は解決されました。

たとえば、Enumクラスがあります。

_public enum FurthitMethod {

    Apple,
    Orange,
    Lemon
}
_

エンティティクラスで、@Enumerated(EnumType.STRING)を定義します。

_@Enumerated(EnumType.STRING)
@Column(name = "Fruits")
public FurthitMethod getFuritMethod() {
    return fruitMethod;
}

public void setFruitMethod(FurthitMethod authenticationMethod) {
    this.fruitMethod= fruitMethod;
}
_

値をデータベースに設定しようとすると、文字列値は「Apple」、「ORANGE」または「LEMON」としてデータベースに保存されます。

2
SaravanaC

私の経験から、enumをどこでも永続化する最も安全な方法は、追加のコード値またはid(@jeebee answerの何らかの進化)を使用することであることがわかります。これは、アイデアの良い例です。

_enum Race {
    HUMAN ("human"),
    ELF ("elf"),
    DWARF ("dwarf");

    private final String code;

    private Race(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}
_

これで、コードで列挙型定数を参照する永続性を使用できます。定数名の一部を変更する場合でも、コード値をいつでも保存できます(例:DWARF("dwarf") to GNOME("dwarf")

さて、この構想をもう少し詳しく見てみましょう。列挙値を見つけるのに役立つユーティリティメソッドを次に示しますが、最初にアプローチを拡張します。

_interface CodeValue {
    String getCode();
}
_

そして、列挙型に実装させます:

_enum Race implement CodeValue {...}
_

これが魔法の検索方法の時です。

_static <T extends Enum & CodeValue> T resolveByCode(Class<T> enumClass, String code) {
    T[] enumConstants = enumClass.getEnumConstants();
    for (T entry : enumConstants) {
        if (entry.getCode().equals(code)) return entry;
    }
    // In case we failed to find it, return null.
    // I'd recommend you make some log record here to get notified about wrong logic, perhaps.
    return null;
}
_

そして、それをチャームのように使用します:Race race = resolveByCode(Race.class, "elf")

2
Metaphore

OR relation for one、enum field。の複数の値。バイトまたはintなどのデータベースに列挙型を格納し、コードでFlagsAttributeを使用する.NETの概念。

http://blogs.msdn.com/b/efdesign/archive/2011/06/29/enumeration-support-in-entity-framework.aspx

1
Kryszal