我正在尝试为java实体添加国际化(多语言)支持。在向每个新字段添加翻译时,我对所有选项都开放,尽可能少的样板代码。我不仅限于JPA,也可以使用hibernate注释。在最坏的情况下,普通sql也适合。可能有一些我没有找到的现成库。 不应该按照下面描述的想法进行操作。
理想情况下,我需要数据库看起来像这样:
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列的表格:id
,locale
和text
。表parent
有一列text
(如果只有一个字段需要i18n,否则会有更多列),其中包含来自i18n.id
的值。我在Parent类中尝试了以下映射:
@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 :(得分: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键不会应用于I18N表,但仍然可以使用
@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"));
}
关于是一个简单的测试(Spring Boot 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 db中的表格:
答案 1 :(得分:1)
似乎Hibernate没有提供开箱即用的任何i18n
支持,因此您可以通过正确的方式为此实现自定义解决方案。
我还可以假设您的目标是以最低的成本为现有项目添加本地化支持。
我建议您在ManyToMany
和Parent
表格上使用i18n
关系。
在这种情况下,您可以根据需要完全独立于Parent
和i18n
表结构,但是每个"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;
.....
}
答案 2 :(得分:0)
您是不是只是使用
错误地指定了@JoinColumn
@JoinColumn(referencedColumnName="id"))
而不是
@JoinColumn(name="id"))
http://docs.oracle.com/javaee/6/api/javax/persistence/JoinColumn.html#referencedColumnName()
在CollectionTable映射中使用时,引用的列位于 包含集合的实体表
所以,基本上你是(重新)在父中定义ID列,而Hibernate使用其默认命名策略在i18n中生成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
答案 3 :(得分:0)
看起来你正在搜索hibernate复合主键。你应该执行这个技巧:
@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
}
不幸的是,我不知道如何将其映射为'locale'地图,但认为可以使用@JoinColumn注释,或者尝试关注@Alan Hay帖子。