我想描述一下在JPA实体中天真地使用Java枚举时出现的令人讨厌的问题。我们来看看这个问题是如何发生的。
首先是域名模型:
假设我有一个Text
JPA实体代表一段文字(小说,新闻文章等)。这是JPA实体:
@Entity
public class Text {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
@Version
@Column(name = "version")
private Integer version;
private String content;
@Enumerated
@ElementCollection
private Set<Style> styles;
//Setters and getters omitted.
对于Text
的实例,可以应用一种或多种样式,例如斜体,粗体等。该样式表示为java enum。
首先,我们假设应用程序以以下枚举开始生命:
public enum Style {
BOLD, ITALIC
}
然后,下面的测试将在关系数据库中插入以下行:
集成测试:
@Test
@Rollback(value=false)
public void inEarlyLifePersist() {
Text text =new Text();
text.setContent("This is my beautiful novel...");
text.setStyles(EnumSet.of(Style.BOLD, Style.ITALIC));
text.persist();
}
文本表格中的数据:
# id, content, version
11, This is my beautiful novel..., 0
* text_style表中的数据:*
# text, styles
11, 0
11, 1
然后,稍后,一些不明智的开发人员决定向我们的STRIKE_THROUGH
枚举添加新样式:Style
,将此新枚举常量/值作为第一个之一:
public enum Style {
STRIKE_THROUGH, BOLD, ITALIC
}
然后是new record is inserted in DB as follows
:
@Test
@Rollback(value=false)
public void afterChangeToEnumPersist() {
Text text =new Text();
text.setContent("This is my beautiful short story...");
text.setStyles(EnumSet.of(Style.STRIKE_THROUGH, Style.BOLD));
text.persist();
}
在文字表格中:
# id, content, version
14, This is my beautiful short story..., 0
和*在text_style表中:*
# text, styles
14, 0
14, 1
显然,域名模型现在严重受损!
我的问题是,如上所述,在域中避免拼写灾难的可能策略是什么 (除了放置{{1}的明显解决方案之外}枚举常量 STRIKE_THROUGH
后)?
编辑1 :显然我不想在数据库中存储字符串(请参阅ITALIC
),原因很明显,即数据检索和存储性能会受到严重影响!
答案 0 :(得分:3)
您需要重新定义enum
,如下所示。
public enum Style {
STRIKE_THROUGH(2), BOLD(0), ITALIC(1)
Style(int code){
this.code=code;
}
}
并实施Hibernate User type以保留code
。
答案 1 :(得分:1)
有一个选项(EnumType.STRING)来使用枚举值的实际名称(由{name()}返回的字符串而不是序数。这样你可以重新组织你的枚举值,但是你被绑定到枚举值的名称。
理想的解决方案是能够声明性地告诉JPA实现使用枚举的任意属性作为数据库标识符。但AFAIK,它在当前的JPA规范中没有提供,在未来的JPA规范中有这样的功能会很棒。
Sajan的回答展示了如何使用特定于Hibernate的功能来实现它。
答案 2 :(得分:1)
Enumerated注释也知道指定EnumType的属性。存在两种类型:EnumType.ORDINAL
和EnumType.STRING
。 ORDINAL是默认值。
所以,如果你按照以下方式进行
@Enumerated(EnumType.STRING)
您将在DB列中看到枚举名称(而不是序数)。当然,您现在容易受到枚举中名称更改的影响。你必死一死,但我认为,名字更好。
答案 3 :(得分:1)
我不明白为什么人们发现枚举名称比其序数更可靠。实际上,有很多很好的理由来重命名枚举(修正错别字,因政治或政治正确性而改名等),但我看不出有任何理由重新安排它们。
重命名和重新排序都会发生,唯一可以帮助的是测试。不幸的是,我能想到的最佳测试将在任何变化时失败。幸运的是,测试可以判断发生了什么,然后很容易修复。
public void testE1IsStable() {
assertEnumUnchanged(E1.class, 4, "bec419c8380dbe9ec3b86a7023a55107");
}
public void testE2IsStable() {
assertEnumUnchanged(E2.class, 3, "1e89e93c6cbdbb7311b814c19d682548");
}
private void assertEnumUnchanged(Class<? extends Enum<?>> enumClass, int expectedCount, String expectedHash) {
final Object[] enumConstants = enumClass.getEnumConstants();
if (expectedCount < enumConstants.length) {
final Object[] shortened = Arrays.copyOf(enumConstants, expectedCount);
assertEquals("Enum constants may be only appended! Ask balteo!",
expectedHash, hashAsString(shortened));
fail("An enum constant has been added! This test needs to be updated. Ask balteo!");
} else if (expectedCount > enumConstants.length) {
fail("Enum constants must not be removed! Ask balteo!");
} else {
assertEquals("Enum constants must not be reordered! If they get renamed, this test must be updated. Ask balteo!",
expectedHash, hashAsString(enumConstants));
}
}
private String hashAsString(Object[] enumConstants) {
final Hasher hasher = Hashing.md5().newHasher();
for (final Object o : enumConstants) hasher.putUnencodedChars(o.toString());
return hasher.hash().toString();
}