Sql 使用JPA/Hibernate实现国际化的最优雅解决方案?
亲爱的Stackflow社区: 我希望在我的实体中存储国际化字符串,我正在努力解决如何实现这一点。我将在这里介绍我能想到的两种解决方案,并希望收到您的反馈 解决方案1:从实体外部化 在此解决方案中,我的数据库中有下表:Sql 使用JPA/Hibernate实现国际化的最优雅解决方案?,sql,hibernate,jpa,internationalization,Sql,Hibernate,Jpa,Internationalization,亲爱的Stackflow社区: 我希望在我的实体中存储国际化字符串,我正在努力解决如何实现这一点。我将在这里介绍我能想到的两种解决方案,并希望收到您的反馈 解决方案1:从实体外部化 在此解决方案中,我的数据库中有下表: LabelId Language VALUE Entity Id WELCOME_TEXT EN "..." Questionnaire 1 WELCOME_TEXT DE
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
我将始终使用以下方法访问此集合:
void setLabels(Entity entity, LabelId labelId, Map<String, String langValues)
Map<String, String> getLabels(Entity entity, LabelId labelId)
然后,我将以以下格式映射实体中的所有字段:
i18n.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;
...
}
inventory.java:
@OneToMany(mappedBy = "questionnaire")
List<i18n> labels;
@OneToMany(mappedBy=“问卷”)
列出标签;
Question.java:
@OneToMany(mappedBy = "question")
List<i18n> labels;
@OneToMany(mappedBy=“问题”)
列出标签;
解决方案3:非解决方案
另一种简单的方法是为每个属性存储这样的地图:
@ElementCollection
Map<String, String> welcomeText;
@ElementCollection
Map<String, String> goodbyeText;
...
@ElementCollection
地图welcomeText;
@元素集合
地图再见文本;
...
这个解决方案在我的数据库中创建了大量的表,这将使查询和维护变得混乱
结论
第一个解决方案为我提供了一个更好的表,但每次我需要标签时都必须通过附加服务检索标签。
第二种解决方案保持实体的代码干净,但SQL表凌乱,需要转换的所有实体都有许多列
你将如何处理这个问题?有更好的解决办法吗
提前感谢所有的反馈 我们通过将每个实体的翻译分开并保持规范化来解决这个问题,因此将翻译从主实体中排除,例如您的实体:
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
@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;
}
表格问题:
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
@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;
}
表格问题\u翻译:
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
@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;
}
实体的Java代码(未测试):
问题:
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
@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;
}
我用描述的第一种方法解决了这个问题。 此外,我使用PreparedStatements缓存查询,它们的性能非常好。除了设置单个翻译外,我还创建了批量编辑和批量检索方法,它们的性能甚至更好。以下是我的解决方案-
您可以看看这个演示项目。桌面或web应用程序?我不确定我是否理解您提问的原因。这不应该真的有什么区别。在这种情况下,此应用程序有许多客户端(多个应用程序、浏览器版本等)。这是关于后端的数据结构。Deskop应用程序应该读取字符串一次并缓存。这是一些波兰transaltion解决方案,使用JPA效果很差。是的,这或多或少是我在解决方案3中写的。我看到的这个解决方案的主要问题是,如果您想转换多个实体中的多个字段,那么数据库中的表数量将大幅增加。特别是如果你正在使用Envers,是的,你是对的,它会增长,但我想这是一种非常干净的方法。我们对此进行了几个小时的讨论,因为您询问了最优雅的解决方案,我更喜欢这个,我们应用了它,结果非常好(对于许多表)。