如何在数据库中保存枚举

use*_*298 115 java database enums

将枚举保存到数据库的最佳方法是什么?

我知道Java提供name()valueOf()方法来枚举值转换为字符串和背部.但是有没有其他(灵活的)选项来存储这些值?

有没有一种聪明的方法可以将枚举变成唯一的数字(ordinal()使用起来不安全)?

更新:

感谢所有令人敬畏和快速的答案!这是我怀疑的.

但请注意"工具包"; 这是一种方式.问题是我必须为我创建的每个Enum类型添加相同的方法.这是很多重复的代码,目前,Java不支持任何解决方案(Java枚举不能扩展其他类).

Ian*_*oyd 156

我们永远不会将枚举存储为数字序数值; 它使调试和支持方式太难了.我们存储转换为字符串的实际枚举值:

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

Suit theSuit = Suit.Heart;

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

然后回读:

Suit theSuit = Suit.valueOf(reader["Suit"]);
Run Code Online (Sandbox Code Playgroud)

问题是过去盯着企业管理器并试图破译:

Name                Suit
==================  ==========
Shelby Jackson      2
Ian Boyd            1
Run Code Online (Sandbox Code Playgroud)

经文

Name                Suit
==================  ==========
Shelby Jackson      Diamond
Ian Boyd            Heart
Run Code Online (Sandbox Code Playgroud)

后者更容易.前者需要获取源代码并查找分配给枚举成员的数值.

是的,它需要更多空间,但枚举成员名称很短,硬盘驱动器很便宜,当你遇到问题时,它更值得帮助.

此外,如果您使用数值,则与它们相关联.您无法在不必强制使用旧数值的情况下很好地插入或重新排列成员.例如,将Suit枚举更改为:

public enum Suit { Unknown, Heart, Club, Diamond, Spade }
Run Code Online (Sandbox Code Playgroud)

必须成为:

public enum Suit { 
      Unknown = 4,
      Heart = 1,
      Club = 3,
      Diamond = 2,
      Spade = 0 }
Run Code Online (Sandbox Code Playgroud)

为了保持存储在数据库中的遗留数值.

如何在数据库中对它们进行排序

问题出现了:假设我想订购价值.有些人可能希望按枚举的序数值对它们进行排序.当然,按枚举的数值排序卡片是没有意义的:

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

Suit
------
Spade
Heart
Diamond
Club
Unknown
Run Code Online (Sandbox Code Playgroud)

这不是我们想要的顺序 - 我们希望它们按枚举顺序排列:

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
Run Code Online (Sandbox Code Playgroud)

如果保存字符串,则需要保存整数值所需的相同工作:

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

Suit
-------
Club
Diamond
Heart
Spade
Unknown
Run Code Online (Sandbox Code Playgroud)

但这不是我们想要的顺序 - 我们希望它们按枚举顺序排列:

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
Run Code Online (Sandbox Code Playgroud)

我的观点是这种排名属于用户界面.如果您根据枚举值对项目进行排序:您做错了什么.

但是如果你想真的这样做,我会创建一个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  |
Run Code Online (Sandbox Code Playgroud)

这样,当您想要更改卡片以使用Kissing Kings New 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        |
Run Code Online (Sandbox Code Playgroud)

现在我们将内部编程细节(枚举名称,枚举值)与用户的显示设置分开:

SELECT Cards.Suit 
FROM Cards
   INNER JOIN Suits ON Cards.Suit = Suits.Suit
ORDER BY Suits.Rank, 
   Card.Rank*Suits.CardOrder
Run Code Online (Sandbox Code Playgroud)

  • 通常会覆盖toString以提供显示值.name()是一个更好的选择,因为它定义为valueOf()的对应物 (21认同)
  • 我强烈不同意这一点,如果需要枚举持久性,那么就不应该坚持名字.只要读回去它就更简单了,而不是名称可以将它强制转换为SomeEnum enum1 =(SomeEnum)2; (8认同)
  • mamu:数字等价变化时会发生什么? (3认同)
  • 我会劝阻任何使用这种方法的人.将自己与字符串表示联系起来会限制代码的灵活性和重构.你最好使用独特的ID.存储字符串也会浪费存储空间. (2认同)
  • @LuisGouveia 我同意你的观点,时间可能会加倍。导致需要“12.37 ms”的查询改为“12.3702 ms”。这就是我所说的*“在噪音中”*。您再次运行查询,需要“13.29 ms”或“11.36 ms”。换句话说,线程调度程序的随机性将极大地淹没理论上任何人都无法看到的任何微优化。 (2认同)

Tom*_*Tom 39

除非您有特定的性能原因要避免它,否则我建议使用单独的表进行枚举.使用外键完整性,除非额外的查找真的杀了你.

西装表:

suit_id suit_name
1       Clubs
2       Hearts
3       Spades
4       Diamonds
Run Code Online (Sandbox Code Playgroud)

球员表

player_name suit_id
Ian Boyd           4
Shelby Lake        2
Run Code Online (Sandbox Code Playgroud)
  1. 如果您将枚举重构为具有行为的类(例如优先级),则您的数据库已经正确地对其进行了建模
  2. 您的DBA很高兴,因为您的架构已规范化(每个玩家存储一个整数,而不是整个字符串,可能有也可能没有拼写错误).
  3. 您的数据库值(suit_id)与枚举值无关,这有助于您处理其他语言的数据.

  • 虽然我同意将它标准化并在数据库中进行约束是很好的,但这确实会导致两个地方的更新添加新值(代码和数据库),这可能会导致更多的开销.此外,如果所有更新都是从Enum名称以编程方式完成的,则拼写错误应该不存在. (13认同)
  • 我同意上述评论.数据库级别的另一种强制机制是编写约束触发器,该触发器将拒绝尝试使用无效值的插入或更新. (3认同)

小智 8

我遇到了同样的问题,我的目标是将枚举字符串值而不是序数值保留到数据库中。

为了解决这个问题,我已经使用@Enumerated(EnumType.STRING)并且我的目标得到了解决。

例如,您有一个Enum班级:

public enum FurthitMethod {

    Apple,
    Orange,
    Lemon
}
Run Code Online (Sandbox Code Playgroud)

在实体类中,定义@Enumerated(EnumType.STRING)

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

public void setFruitMethod(FurthitMethod authenticationMethod) {
    this.fruitMethod= fruitMethod;
}
Run Code Online (Sandbox Code Playgroud)

当您尝试将值设置为数据库时,字符串值将作为“ APPLE”、“ ORANGE”或“ LEMON”保存到数据库中。


oxb*_*kes 5

我认为这里唯一安全的机制是使用String name()值.写入数据库时​​,可以使用sproc插入值,在读取时使用View.以这种方式,如果枚举改变,则在sproc/view中存在一个间接级别,以便能够将数据作为枚举值呈现而不将"强加"在DB上.


too*_*kit 5

正如你所说,序数有点冒险.考虑例如:

public enum Boolean {
    TRUE, FALSE
}

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

如果您将其存储为序数,则可能包含以下行:

> SELECT STATEMENT, TRUTH FROM CALL_MY_BLUFF

"Alice is a boy"      1
"Graham is a boy"     0
Run Code Online (Sandbox Code Playgroud)

但是如果更新布尔会发生什么?

public enum Boolean {
    TRUE, FILE_NOT_FOUND, FALSE
}
Run Code Online (Sandbox Code Playgroud)

这意味着你所有的谎言都会被误解为"未找到文件"

最好只使用字符串表示

  • +1参考http://thedailywtf.com/Articles/What_Is_Truth_0x3f_.aspx (3认同)

Jee*_*Bee 5

我们只存储枚举名称本身。它更具可读性。

我们确实向枚举添加了一个附加属性,其中枚举具有一组有限的值。例如,在下面的枚举中,我们使用char属性来表示数据库中的枚举值(achar比数字值更有意义):

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

当您有很多值时,您可以在Map枚举内部使用一个枚举来保持该getFromXYZ方法较小。


小智 5

对于大型数据库,我不愿意失去数字表示的大小和速度优势。我经常会得到一个代表枚举的数据库表。

您可以通过声明外键来强制数据库一致性 - 尽管在某些情况下最好不要将其声明为外键约束,因为这会给每个事务带来成本。您可以在您选择的时间定期进行检查,以确保一致性:

SELECT reftable.* FROM reftable
  LEFT JOIN enumtable ON reftable.enum_ref_id = enumtable.enum_id
WHERE enumtable.enum_id IS NULL;
Run Code Online (Sandbox Code Playgroud)

该解决方案的另一半是编写一些测试代码来检查 Java 枚举和数据库枚举表是否具有相同的内容。这留给读者作为练习。

  • 假设枚举名称的平均长度为 7 个字符。您的“enumID”是四个字节,因此通过使用名称,每行可以获得额外的三个字节。3 字节 x 100 万行为 3MB。 (3认同)