将枚举保存到数据库的最佳方法是什么?
我知道Java提供name()
和valueOf()
方法将枚举值转换为String并返回。但是有没有其他(灵活的)选项来存储这些值?
是否有一种智能方法可将枚举变为唯一数字(ordinal()
使用不安全)?
更新
感谢所有精彩而快速的答案!就像我怀疑的那样。
但请注意'工具包';这是一种方式。问题是我必须为我创建的每个Enum类型添加相同的方法。这是很多重复的代码,目前,Java不支持任何解决方案(Java枚举不能扩展其他类)。
答案 0 :(得分:151)
我们从不将枚举存储为数字序数值;它使调试和支持方式太难了。我们存储转换为string的实际枚举值:
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"]);
问题是过去盯着企业管理器并试图破译:
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 |
这样,当您想要更改卡片以使用 亲吻国王新甲板顺序时,您可以更改它以用于显示目的而不会丢弃所有数据:
| 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
答案 1 :(得分:37)
除非您有特定的性能原因要避免它,否则我建议使用单独的表进行枚举。使用外键完整性,除非额外查找真的杀了你。
suit_id suit_name
1 Clubs
2 Hearts
3 Spades
4 Diamonds
player_name suit_id
Ian Boyd 4
Shelby Lake 2
suit_id
)与枚举值无关,这有助于您处理其他语言的数据。答案 2 :(得分:5)
我认为这里唯一安全的机制是使用String name()
值。写入数据库时,可以使用sproc插入值,在阅读时使用View。以这种方式,如果枚举更改,则在sproc / view中有一个间接级别,以便能够将数据作为枚举值显示,而不会在DB上“强加”它。
答案 3 :(得分:5)
正如你所说,序数有点危险。考虑例如:
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
}
这意味着你的所有谎言都会被误解为“未找到文件”
最好只使用字符串表示
答案 4 :(得分:3)
我们只存储枚举名称 - 它更具可读性。
我们确实存储了存在特定值的枚举的特定值,例如,这个枚举具有一组有限的状态,我们使用char来表示(比数值更有意义):< / p>
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;
}
}
}
当你有很多值时,你需要在你的枚举中有一个Map来保持getFromXYZ方法的小。
答案 5 :(得分:3)
对于大型数据库,我不愿意失去数字表示的大小和速度优势。我经常最终得到一个代表Enum的数据库表。
您可以通过声明外键来强制实施数据库一致性 - 尽管在某些情况下,最好不要将其声明为外键约束,这会对每个事务造成成本。您可以通过以下方式定期进行检查,确保一致性:
SELECT reftable.* FROM reftable
LEFT JOIN enumtable ON reftable.enum_ref_id = enumtable.enum_id
WHERE enumtable.enum_id IS NULL;
此解决方案的另一半是编写一些测试代码,用于检查Java枚举和数据库枚举表是否具有相同的内容。这是留给读者的练习。
答案 6 :(得分:2)
如果将枚举保存为数据库中的字符串,则可以创建实用程序方法以(de)序列化任何枚举:
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);
答案 7 :(得分:2)
我所有的经验告诉我,在任何地方坚持枚举最安全的方法是使用额外的代码值或id(某种@jeebee答案的演变)。这可能是一个很好的例子:
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")
到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")
答案 8 :(得分:2)
我遇到了同样的问题,我的目标是将Enum String值保存到数据库而不是Ordinal值。
为了解决这个问题,我使用了@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;
}
当您尝试将值设置为数据库时,字符串值将持久保存到数据库中作为&#34; APPLE
&#34;,&#34; ORANGE
&#34;或&#34; LEMON
&#34;。
答案 9 :(得分:1)
具有OR关系的多个值,用于一个枚举字段。 .NET的概念,在数据库中存储枚举类型,如字节或int,并在代码中使用FlagsAttribute。
http://blogs.msdn.com/b/efdesign/archive/2011/06/29/enumeration-support-in-entity-framework.aspx
答案 10 :(得分:0)
您可以在枚举常量中使用一个额外的值,该值可以在名称更改和使用枚举时均幸免于难:
public enum MyEnum {
MyFirstValue(10),
MyFirstAndAHalfValue(15),
MySecondValue(20);
public int getId() {
return id;
}
public static MyEnum of(int id) {
for (MyEnum e : values()) {
if (id == e.id) {
return e;
}
}
return null;
}
MyEnum(int id) {
this.id = id;
}
private final int id;
}
要从枚举中获取ID:
int id = MyFirstValue.getId();
要从ID获取枚举:
MyEnum e = MyEnum.of(id);
我建议使用无意义的值,以免在必须更改枚举名称的情况下造成混淆。
在上面的示例中,我使用了“基本行编号”的一些变体,因此留有空格,因此编号可能会与枚举保持相同的顺序。
此版本比使用辅助表更快,但是它使系统更依赖于代码和源代码知识。
为此,您也可以在数据库中设置一个带有枚举ID的表。或者以其他方式,在向表中添加行时从表中选择枚举的ID。
边注:始终确认您没有设计应存储在数据库表中并保留为常规对象的内容。如果您可以想象此时必须在枚举中添加新的常量,那么在设置它时,这表明您最好创建一个常规对象和一个表。