2

我想描述在 JPA 实体的上下文中天真地使用 Java 枚举时发生的一个令人讨厌的问题。让我们来看看这个问题是如何发生的。

首先是域模型

假设我有一个TextJPA 实体,它代表一段文本(小说、新闻文章等)。这是 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 枚举。

首先,我们假设应用程序从以下枚举开始其生命

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

显然,领域模型现在严重受损!

我的问题是有哪些可能的策略来避免域中的拼写灾难,就像上面的情况一样(除了将STRIKE_THROUGH枚举常量放在之后 ITALIC的明显解决方案)?

编辑 1:显然,出于明显的性能原因,我不想EnumType.STRING在我的数据库中存储字符串(请参阅 参考资料),即数据检索和存储性能会受到严重影响!

4

4 回答 4

3

您需要在enum下面重新定义您的喜欢。

public enum Style {
    STRIKE_THROUGH(2), BOLD(0), ITALIC(1)

    Style(int code){
      this.code=code;
    }
}

并实现一个Hibernate User 类型来持久化code.

于 2013-09-25T11:19:51.417 回答
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();
}
于 2014-01-24T12:54:29.587 回答
1

有一个选项 (EnumType.STRING) 可以使用枚举值的实际名称(由 { name() } 返回的字符串而不是序数。这样您就可以重新组织枚举值,但是您会被绑定到枚举值。

理想的解决方案是能够以声明方式告诉 JPA 实现使用枚举的任意属性作为数据库标识符。但是 AFAIK,它没有在当前的 JPA 规范中提供,在未来的 JPA 规范中拥有这样的功能会很棒。

Sajan 的回答展示了如何使用特定于 Hibernate 的特性来实现它。

于 2013-09-25T11:42:38.433 回答
1

Enumerated注解还知道一个指定EnumType的属性。存在两种类型:EnumType.ORDINALEnumType.STRING。ORDINAL 是默认值。

因此,如果您按照以下方式进行操作

    @Enumerated(EnumType.STRING)

您将在 DB 列中看到枚举名称(而不是序数)。当然,您现在很容易受到枚举中名称更改的影响。你必须死一死,但我认为,名字更好。

于 2013-09-25T11:45:52.587 回答