2016-09-26 78 views
8

我正在嘗試向Java實體添加國際化(多語言)支持。在向每個新字段添加翻譯時,我都會盡可能使用盡可能少的樣板代碼。我不僅限於JPA,也可以使用hibernate註釋。在最糟糕的情況下,普通的sql也適用。可能有一些我沒有找到的準備好的庫。 它不應該遵循我的想法描述如下。如何國際化Hibernate實體

理想情況下,我需要的數據庫看起來像這樣:

i18n 
+------+--------+------+ 
| id | locale | text | 
+------+--------+------+ 
| 1 | en | foo | 
+------+--------+------+ 
| 1 | de | bar | 
+------+--------+------+ 
| 2 | en | foo2 | 
+------+--------+------+ 
| 2 | de | bar2 | 
+------+--------+------+ 

parent 
+------+------+ 
| id | text | 
+------+------+ 
| 99 | 1 | 
+------+------+ 
| 100 | 2 | 
+------+------+ 

i18n是應該只包含3列的表格:idlocaletext。表parent有一個列text(如果只有一個字段需要i18​​n,否則包含更多列),其中包含i18n.id的值。我試圖在父類中的下列映射:

@ElementCollection @CollectionTable(name="i18n", joinColumns = @JoinColumn(referencedColumnName="id")) 
@MapKeyColumn(name="locale") @Column(name="text") 
public Map<String, String> text = newHashMap(); 

看來當DDL一代被禁用,我自己創建表,但如果啓用了DDL生成,它生成不必要的列i18n.parent_id和制約工作它:

ALTER TABLE PUBLIC.I18N ADD CONSTRAINT 
PUBLIC.FK_HVGN9UJ4DJOFGLT8L78BYQ75I FOREIGN KEY(PARENT_ID) INDEX 
PUBLIC.FK_HVGN9UJ4DJOFGLT8L78BYQ75I_INDEX_2 REFERENCES 
PUBLIC.PARENT(ID) NOCHECK 

我該如何擺脫這額外的列?是否有可能避免將i18n表引用到parent表中?此鏈接使難以重用i18n表。我需要在i18n表中保存一些鑑別值,或者在整個數據庫中使用GUID,因爲不同表中的ID會發生衝突。第一個選項意味着大量的樣板代碼。第二種選擇意味着當前項目需要完成很多工作。

我需要一種可重複使用的方法來將i18n添加到實體。我的父類將看起來像這樣。並且會有幾個這樣的父類不同的字段集必須國際化。

@Entity 
public class Parent { 

    @Id @GeneratedValue 
    public Long id; 

    public String title; // must be in internationalized 
    public String text; // must be in internationalized 
    public String details; // must be in internationalized 

    // ... other fields 
} 
+0

什麼是不必要的parent_id字段?您的元素集合需要某種方式將生成的表內的行與父實體關聯 - 一個外鍵。也許顯示你希望數據庫看起來如何,有人可以幫助你弄清楚如何映射實體。 – Chris

+0

我添加了所需的數據庫說明。我需要一個可重用的i18n表,以便與父母建立一對多的關係。我需要能夠使用相同的i18n表來存儲其他字段的翻譯。 Parent.text&i18n.id應該是將生成的表中的行與父實體相關聯的方式。外鍵約束可能只存在於parent.text中。 – user1603038

+0

你可以展示完整的'i18n'類嗎? –

回答

0

你不只是使用

@JoinColumn(referencedColumnName="id")) 

指定@JoinColumn錯誤,而不是

@JoinColumn(name="id")) 

http://docs.oracle.com/javaee/6/api/javax/persistence/JoinColumn.html#referencedColumnName()

當在CollectionTable映射中使用,引用的列是 實體包含集合

所以表,基本上你是(重新)定義父ID列和Hibernate使用其默認的命名策略,國際化生成的FK列。

使用此:

@Entity 
@Table(name = "parent") 
public class Parent { 

    @Id 
    private Long id; 

    @ElementCollection 
    @CollectionTable(name = "i18n", foreignKey = @ForeignKey(name = "ii8n_to_parent_fk"), joinColumns = @JoinColumn(name = "id")) 
    @MapKeyColumn(name = "locale") 
    @Column(name = "text") 
    public Map<String, String> text = new HashMap<>(); 

} 

生成以下內容:

19:29:08.890 [main] DEBUG jdbc.sqlonly - org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:51) 
3. create table i18n (id bigint not null, text varchar(255), locale varchar(255) not null, primary 
key (id, locale)) 

19:29:09.464 [main] DEBUG jdbc.sqlonly - org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:51) 
3. alter table i18n add constraint ii8n_to_parent_fk foreign key (id) references parent 
+0

外鍵(id)引用parent - 這意味着i18n.id只能包含parent.id值,但它不是我所需要的。我需要parent.text上的外鍵只包含i18n.id值。 – user1603038

+1

你可以發佈你的完整的實體類嗎?然後親自解釋**你究竟看到了那個工作?你有250提供和零答案賞金。這是什麼告訴你的? –

+0

我添加了一個父類的例子,並更新了這個問題,但是我需要一個不依賴父類結構的通用解決方案。 – user1603038

2

在數據庫級別收集實例由持有集合的實體的外鍵加以辨別。這個外鍵被稱爲集合表的一個或多個集合鍵列。

,所以我想你想禁用您提出forgien密鑰生成,簡單地你可以用這個

@Entity 
public class Parent { 

    @Id 
    @GeneratedValue 
    public Long id; 

    @ElementCollection 
    @CollectionTable(name = "i18n", foreignKey = @ForeignKey(name = "none"), joinColumns = @JoinColumn(name = "id")) 
    @MapKeyColumn(name = "locale") 
    @Column(name = "text") 
    public Map<String, String> text = new HashMap<>(); 

    public Long getId() { 
     return id; 
    } 

    public void setId(Long id) { 
     this.id = id; 
    } 

    public Map<String, String> getText() { 
     return text; 
    } 

    public void setText(Map<String, String> text) { 
     this.text = text; 
    } 
} 

做到這一點。然後,當你確認生成的DDL中,forgien關鍵它不applyed到在國際化的表,但仍獲得的能力

@Test 
public void testParent() { 
    Parent p = new Parent(); 
    HashMap<String, String> text = new HashMap<>(); 
    text.put("en", "foo"); 
    text.put("de", "bar"); 
    p.setText(text); 

    entityManager.persist(p); 
    entityManager.flush(); 

    Parent parent = entityManager.find(Parent.class, p.getId()); 
    System.out.println("en: " + parent.getText().get("en")); 
    System.out.println("de: " + parent.getText().get("de")); 
} 

的是一個簡單的測試(春季啓動1.4版本),並會在控制檯看到輸出:

Hibernate: 
    create table i18n (
     id bigint not null, 
     text varchar(255), 
     locale varchar(255) not null, 
     primary key (id, locale) 
    ) 
Hibernate: 
    create table parent (
     id bigint generated by default as identity, 
     primary key (id) 
    ) 
...... 
Hibernate: 
    insert 
    into 
     parent 
     (id) 
    values 
     (null) 
Hibernate: 
    insert 
    into 
     i18n 
     (id, locale, text) 
    values 
     (?, ?, ?) 
Hibernate: 
    insert 
    into 
     i18n 
     (id, locale, text) 
    values 
     (?, ?, ?) 
en: foo 
de: bar 

這是H2分貝表:

enter image description here

+0

在你的解決方案中'父'表只有'ID'字段,沒有其他字段保持到'i18n'表的鏈接。似乎在測試中,您可以從'i18n'表中分配給'text'映射的所有值,因此它可以工作。可能你從'i18n.id'鏈接到了'parent.id',兩者都是空值。這不是一個可行的解決方案。 – user1603038

+0

@ user1603038你可以檢查DDL,在'parent'' i18n'表中沒有任何鏈接,關係應該是'parent.id'和'i18n.id'的文字值。 –

0

看來,Hibernate並不提供任何i18n支持開箱即用,讓你以正確的方式來實現這種定製的解決方案。 我還可以假設您的目標是以最低的成本爲現有項目添加本地化支持。

我建議你在Parenti18n表上使用ManyToMany關係。

在這種情況下,你是完全獨立於Parenti18n表結構,你想要的,但有一些開銷額外加入每各"Parent",其中包含PK參考對形成i18n"Parent"表的表。也可以使用ManyToMany的方法記錄表格i18n可以在不同的"Parent"表中重複使用。

所以,你的表結構可能看起來像這樣:

i18n 
+------+--------+------+ 
| id | locale | text | 
+------+--------+------+ 
| 1 | en | foo | 
+------+--------+------+ 
| 2 | de | bar | 
+------+--------+------+ 
| 3 | en | foo2 | 
+------+--------+------+ 
| 4 | de | bar2 | 
+------+--------+------+ 

i18n_parent 
+-------------+---------+ 
| text_id | i18n_id | 
+-------------+---------+ 
|  1  | 1  | 
+------+------+---------+ 
|  1  | 2  | 
+------+------+---------+ 
|  2  | 3  | 
+------+------+---------+ 
|  2  | 4  | 
+------+------+---------+ 


parent 
+------+------+ 
| id | text | 
+------+------+ 
| 99 | 1 | 
+------+------+ 
| 100 | 2 | 
+------+------+ 

實體的代碼示例:

@Entity 
public class Parent { 

    @Id 
    @GeneratedValue 
    public Long id; 

    @ManyToMany 
    @JoinTable(name = "i18n_parent", 
      joinColumns = @JoinColumn(name = "text_id"), 
      inverseJoinColumns = @JoinColumn(name = "i18n_id")) 
    @MapKey(name = "locale") 
    private Map<String, LocalizedTextEntity> text = new HashMap<>(); 

    .....  
} 

@Entity 
@Table(name = "i18n") 
public class LocalizedTextEntity { 

    @Id 
    @GeneratedValue 
    public Long id; 

    @Column(name = "locale") 
    private String locale; 

    @Column(name = "text") 
    private String text; 

    ..... 
} 
0

看起來像你正在尋找冬眠複合主鍵。您應該執行這一招:

@Embeddable 
public class LocaleForeignKey { 
    Integer id; 
    String locale; 
} 

@Entity 
public class I18n { 
    @AttributeOverrides({ 
     @AttributeOverride(name="id", column = @Column(name="id")) 
     @AttributeOverride(name="locale", column = @Column(name="locale")) 
    }) 
    @EmbeddedId 
    LocaleForeignKey id; 

    String text; 
    ...getters-setters 
} 

不幸的是,我不知道如何將其對應爲「區域」的地圖,但認爲這可能與@JoinColumn註解,還是儘量遵循@Alan乾草後。