如何国际化休眠实体

2022-09-03 16:01:22

我正在尝试向Java实体添加国际化(多种语言)支持。在向每个新字段添加翻译时,我对使用尽可能少的样板代码的任何选项持开放态度。我不仅限于JPA,也可以使用休眠注释。在最坏的情况下,普通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 列的表:、 和 。表有一列(如果只有一个字段需要 i18n,否则会包含 来自 的值。我在父类中尝试了以下映射:idlocaletextparenttexti18n.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

如何摆脱这个额外的列?是否有可能避免从一个表到另一个表的引用?此链接使得重用表变得困难。我要么需要在表中保留一些鉴别器值,要么在整个数据库中使用GUID,因为不同表中的id会发生冲突。第一个选项意味着大量的样板代码。第二个选项意味着在当前项目中需要完成大量工作。i18nparenti18ni18n

我需要一种可重用的方式将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
}

答案 1

在数据库级别,集合实例由拥有集合的实体的外键区分。此外键称为集合表的集合键列。

因此,我想您想禁用您提议的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时,伪造的键它没有应用于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


答案 2

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

我可以建议你在和表上使用关系。ManyToManyParenti18n

在这种情况下,您可以根据需要完全独立于表和表结构,但是每个表都有一些额外的连接表的开销,其中包含PK引用对表单和表。此外,使用方法记录表单可以在不同的表中重用。Parenti18n"Parent"i18n"Parent"ManyToManyi18n"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;

    .....
}

推荐