如何国际化Hibernate实体

时间:2016-09-26 13:50:28

标签: java hibernate jpa internationalization

我正在尝试为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列的表格:idlocaletext。表parent有一列text(如果只有一个字段需要i18​​n,否则会有更多列),其中包含来自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
}

4 个答案:

答案 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中的表格:

enter image description here

答案 1 :(得分:1)

似乎Hibernate没有提供开箱即用的任何i18n支持,因此您可以通过正确的方式为此实现自定义解决方案。 我还可以假设您的目标是以最低的成本为现有项目添加本地化支持。

我建议您在ManyToManyParent表格上使用i18n关系。

在这种情况下,您可以根据需要完全独立于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;

    .....
}

答案 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帖子。