2013-09-25 12 views
2

我想描述在JPA實體的上下文中使用Java枚舉的時發生的令人討厭的問題。我們來看看這個問題是如何發生的。在領域模型中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枚舉。

首先,我們假設應用開始其生命具有以下枚舉

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 enum把這個新的枚舉常量/值作爲第一個:

public enum Style { 
    STRIKE_THROUGH, BOLD, ITALIC 
} 

然後a 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)在我的數據庫的明顯性能方面的原因,即數據檢索和存儲性能將受到嚴重影響!

+0

如果你不能相信你的開發者,沒有什麼能夠阻止他們搞亂你的處理'情況'的映射。評論是我能想到的唯一方法,讓未來的代碼維護人員知道訂單是否有原因,以及QA測試。 – Chris

+0

我不得不不同意Chris的思路。評論應該保留在你無法控制的事情上,並不令人意外。您的枚舉聲明的順序不應該是可靠的信息。當然你可以評論這個(以及其他所有枚舉),但這是一個糟糕的解決方案,只會增加開發人員必須處理的大量瑣事。我們試圖減少事情的數量,以便他們可以花費他們的心理週期而不是生產力。 – corsiKa

+0

@corsiKa:我寧可不同意。如果您發現枚舉聲明順序不可靠,那麼您肯定會發現它們的名稱,甚至下面答案中的輔助數字都不可靠。但是你已經排除了所有的解決方案,遊戲結束了。我同意評論不夠,請參閱我的答案。 – maaartinus

回答

2

您需要重新定義您的enum,如下所示。

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

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

和實施Hibernate User type堅持的code

+0

這可能會有所幫助,但您爲什麼認爲改變順序的開發人員不會更改「代碼」? – maaartinus

1

有一個選項(EnumType.STRING)使用枚舉值的實際名稱(由{name()}返回的字符串而不是序號。這樣您可以重新組織枚舉值,但是然後綁定到枚舉值的名稱

理想的解決方案是能夠聲明性地告訴JPA實現使用enum的任意屬性作爲數據庫標識符。但是AFAIK,它沒有在當前的JPA中提供在未來的JPA規範中擁有這樣的功能將會非常棒。

Sajan的答案顯示瞭如何使用Hibernate特有的功能來實現該功能

+0

關於EnumType.STRING的相同評論:這會殺死數據庫性能:即數據檢索和存儲! – balteo

+0

是的,目前規範所提供的解決方案都不是理想的。 –

1

Enumerated註釋也知道指定EnumType的屬性。存在兩種類型:EnumType.ORDINALEnumType.STRING。 ORDINAL是默認的。

所以,如果你這樣做以下方式

@Enumerated(EnumType.STRING) 

,你會看到在DB列枚舉名(不是序號)。當然,你現在容易受到枚舉中名稱變化的影響。你必須死一場,但我認爲,名字更好。

+0

這會殺死數據庫性能:即數據檢索和存儲! – balteo

+0

@balteo任何基準證明你的論文? – Jagger

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(); 
}