使用JPA / Hibernate实现国际化最优雅的解决方案?

时间:2018-04-19 09:00:53

标签: sql hibernate jpa internationalization

亲爱的Stackflow社区,

我希望将国际化字符串存储在我的实体中,而我正在努力解决这个问题。我将在这里描述我能想到的两个解决方案,并希望得到您的反馈。

解决方案1:从实体外部化

在这个解决方案中,我的数据库中有以下表格:

skip

我总是使用以下方法访问此集合:

LabelId         Language    VALUE   Entity              Id

WELCOME_TEXT    EN          "..."       Questionnaire   1
WELCOME_TEXT    DE          "..."       Questionnaire   1
GOODBYE_TEXT    EN          "..."       Questionnaire   1
GOODBYE_TEXT    DE          "..."       Questionnaire   1

QUESTION_TITLE  EN          "..."       Question        12
QUESTION_TITLE  DE          "..."       Question        12

OPTION_NAME     EN          "..."       Option          23
OPTION_NAME     DE          "..."       Option          23

FACTOR_NAME     EN          "..."       Factor          11
FACTOR_NAME     DE          "..."       Factor          11

解决方案2:使用元素集合

在此解决方案中,所有实体都将Map存储在同一个表中:

void setLabels(Entity entity, LabelId labelId, Map<String, String langValues)
Map<String, String> getLabels(Entity entity, LabelId labelId)

然后,我将按以下格式映射实体中的所有字段:

i18n.java:

LabelId         Language    VALUE   questionnaire_id    factor_id   question_id option_id

WELCOME_TEXT    EN          "..."       1
WELCOME_TEXT    DE          "..."       1
GOODBYE_TEXT    EN          "..."       1
GOODBYE_TEXT    DE          "..."       1

QUESTION_TITLE  EN          "..."                                   12
QUESTION_TITLE  DE          "..."                                   12

OPTION_NAME     EN          "..."                                               23
OPTION_NAME     DE          "..."                                               23

FACTOR_NAME     EN          "..."                       11
FACTOR_NAME     DE          "..."                       11

Questionnaire.java:

@Entity 
@Table(uniqueConstraints={@UniqueConstraint(columnNames={"label", "language", "questionnaire_id", "question_id", ... })})
public class i18n extends PersistentObject { // where it gets ID from
    Label label; // enum

    Language language; // enum

    String value; // the actual text

    // bi-directional links to the linked entities
    @ManyToOne Questionnaire questionnaire;
    @ManyToOne Question question;
    ...
}

Question.java:

@OneToMany(mappedBy = "questionnaire")
List<i18n> labels;

解决方案3:非解决方案

另一种简单的方法是简单地为每个属性存储这样的地图:

@OneToMany(mappedBy = "question")
List<i18n> labels;

此解决方案在我的数据库中创建了一个TON表,这将使查询和维护变得混乱。

结论

第一个解决方案为我提供了一个更好的表格,代价是每次需要时都需要通过附加服务检索标签。 第二个解决方案使实体的代码保持干净,但SQL表格杂乱,所有需要翻译的实体都有许多列。

你会怎么做?有更好的解决方案吗?

提前感谢所有反馈!

3 个答案:

答案 0 :(得分:1)

我们通过分离每个实体的翻译并保持规范化来解决这个问题,因此排除了主要实体的翻译,因此对于您的一个示例权利:

CREATE TABLE question(
  id SERIAL PRIMARY KEY,
  origin_date DATE; --Put all fields that don't change via language here
);

-- Create a translation table for the above entity and put all fields that 
-- change through internationalization here for example name and description

CREATE TABLE question_translation(
  id SERIAL PRIMARY KEY,
  question_id int NOT NULL REFERENCES question(id),
  language_id int NOT NULL REFERENCES language(id), --we have a separate table for languages so normalisation is provided
  name TEXT,
  description TEXT,
  UNIQUE(question_id , language_id) 
);

这两个表的示例数据现在是:

表格语言(例如):

id    iso_a2_code
------------------
5     DE
12    EN

表格问题:

 id    date
 ----------------
 1     19.04.2018

表格问题翻译:

id     question_id    language_id    name    description
--------------------------------------------------------- 
1      1              5              TestDE  Description in DE
2      1              12             TestEN  Description in EN

实体的JavaCode(未测试):

<强>问题:

@Entity
@Table(name = "question")
public class Question {

    @Id
    @Column
    @NotNull
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    @OneToMany(mappedBy = "question", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private Set<QuestionTranslation> questionTranslations = new HashSet<>();

    @Column
    @Convert(converter = LocalDateConverter.class)
    private LocalDate date;

}

问题翻译:

@Entity
@Table(name = "question_translation")
public class QuestionTranslation  {

    @Id
    @Column
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String name;

    @Column
    private String description;

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
    @JoinColumn(name = "question_id")
    private Question question;

    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.REFRESH)
    @JoinColumn(name = "language_id")
    private LanguageEntity language;

}

答案 1 :(得分:1)

我用所描述的第一种方法解决了这个问题。 另外,我使用PreparedStatements缓存了查询,并且它们的表现非常好。除了设置单个翻译之外,我还创建了批量编辑和批量检索方法,这些方法表现得更好。

答案 2 :(得分:-1)

这是我的解决方法-https://github.com/azerphoenix/spring-i18n-demo

您可以看一下这个演示项目。