java - 用于国际化的 JPA 数据库结构

标签 java jpa

我正在尝试通过 JPA 实现一种简单的国际化方法。我想要一个翻译后的字符串表,我可以在多个表的多个字段中引用它。因此,所有表中出现的所有文本都将替换为对已翻译字符串表的引用。结合语言 id,这将在翻译字符串表中为该特定字段提供一个唯一的行。例如,考虑一个具有实体 Course 和 Module 的模式,如下所示:-

类(class) int course_id, 整数名称, 内部描述

模块 int module_id, 内部名称

course.name、course.description 和 module.name 都引用了翻译字符串表的 id 字段:-

TranslatedString 内部编号, 字符串朗, 字符串内容

这一切看起来很简单。我为所有可以国际化的字符串得到一个表,并且该表用于所有其他表。

我如何使用 eclipselink 2.4 在 JPA 中执行此操作?

我看过嵌入式 ElementCollection,ala this... JPA 2.0: Mapping a Map - 这不完全是我想要的,因为它看起来像是将翻译后的字符串表与拥有表的 pk 相关联。这意味着我每个实体只能有一个可翻译的字符串字段(除非我将新的连接列添加到可翻译的字符串表中,这违背了这一点,这与我试图做的相反)。我也不清楚这将如何跨实体工作,大概每个实体的 ID 都必须使用数据库范围的序列来确保可翻译字符串表的唯一性。

顺便说一句,我尝试了该链接中列出的示例,但它对我不起作用 - 一旦实体添加了 localizedString 映射,坚持它会导致客户端崩溃但服务器上没有明显错误边,数据库中没有任何内容:S

到目前为止,我已经在房子周围转了大约 9 个小时,我看过这个 Internationalization with Hibernate这似乎试图做与上面的链接相同的事情(没有表定义很难看出他取得了什么成就)。在这一点上,我们将不胜感激...

编辑 1 - 重新 AMS anwser 下面,我不确定是否真的解决了这个问题。在他的示例中,它将描述文本的存储留给了其他一些进程。这种方法的想法是实体对象采用文本和语言环境,这(不知何故!)最终出现在可翻译字符串表中。在我给出的第一个链接中,这个人试图通过使用嵌入式 map 来做到这一点,我认为这是正确的方法。他的方法虽然有两个问题——一个似乎行不通!两个如果它确实有效,它将 FK 存储在嵌入式表中而不是相反(我想,我无法运行它所以我无法确切地看到它是如何持续存在的)。我怀疑正确的方法最终是用 map 引用代替每个需要翻译的文本( map 是语言环境-> 内容),但我看不出如何以允许在一个实体中使用多个 map 的方式做到这一点(在可翻译字符串表中没有对应的多列)...

最佳答案

(我是 Henno,他回复了 hwellman 的博客。)我最初的方法与您的方法非常相似,并且它完成了工作。它满足任何实体的任何字段都可以引用具有通用数据库表的本地化字符串映射的要求,而不必引用其他更具体的表。事实上,我还将它用于我们产品实体中的多个字段(名称、描述、详细信息)。我还有一个“问题”,即 JPA 生成的表仅包含一个主键列和一个用于引用此 ID 的值的表。使用 OpenJPA,我不需要虚拟列:

public class StringI18N {

    @OneToMany(mappedBy = "parent", cascade = ALL, fetch = EAGER, orphanRemoval = true)
    @MapKey(name = "locale")
    private Map<Locale, StringI18NSingleValue> strings = new HashMap<Locale, StringI18NSingleValue();
...

OpenJPA 只是将 Locale 存储为字符串。因为我们真的不需要额外的实体 StringI18NSingleValue,所以我认为使用 @ElementCollection 的映射更优雅一些。

但有一个问题您必须注意:您是否允许与多个实体共享 Localized 实体,以及当拥有实体被删除时如何防止孤立的 Localized 实体?仅仅使用 cascade all 是不够的。我决定尽可能将 Localized 视为“值对象”,并且不允许它与其他实体共享,这样我们就不必考虑对同一个 Localized 的多个引用,我们可以安全地使用孤儿移除。所以我的本地化字段映射如下:

@OneToOne(cascade = ALL, orphanRemoval = true)

根据我的用例,我还使用 fetch = EAGER/LAZY 和 optional = false 或 true。使用 optional = false 时,我使用 @JoinColumn(nullable=false) 因此 OpenJPA 在连接列上生成非空约束。

每当我确实需要将 Localized 复制到另一个实体时,我不会使用相同的引用,而是创建一个具有相同内容但还没有 ID 的新 Localized 实例。否则你可能很难调试 changin 如果你不这样做你仍然与多个实体共享一个实例的问题,你可能会遇到令人惊讶的错误,其中更改本地化字符串会更改另一个实体的另一个字符串。

目前一切顺利,但在实践中我发现 OpenJPA 在选择包含一个或多个本地化字符串的实体时有 N+1 个选择问题。它不能有效地获取元素集合(我将其报告为 https://issues.apache.org/jira/browse/OPENJPA-1920 )。该问题可能通过使用 Map 来解决。但是,OpenJPA 也无法有效地获取 A 1..1 B 1..* C 形式的结构,这也是此处发生的情况(我将其报告为 https://issues.apache.org/jira/browse/OPENJPA-2296 )。这会严重影响您的应用程序的性能。

其他 JPA 提供程序可能有类似的 N+1 选择问题。如果您关心获取类别的性能,我会检查用于获取类别的查询数量是否取决于实体数量。我知道使用 Hibernate 你可以强制批量获取或子选择来解决这类问题。我也知道 EclipseLink 具有类似的功能,这些功能可能有效也可能无效。

出于解决这个性能问题的绝望,我实际上不得不接受一个我不太喜欢的设计:我只是为我必须支持的每种语言添加一个 String 字段到 Localised。对我们来说这是可能的,因为我们目前只需要支持几种语言。这导致只有一个(非规范化)本地化表。然后,JPA 可以在查询中高效地加入本地化表,但这对于许多语言来说都无法很好地扩展,并且不支持任意数量的语言。为了可维护性,我保持 Localized 的外部接口(interface)不变,只是将实现从 Map 更改为 field-per-language,以便我们将来可以轻松切换回来。

关于java - 用于国际化的 JPA 数据库结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13426273/

相关文章:

java - 有没有更简单的方法来使用 BETWEEN 进行 JPQL 查询以在属性中包含 NULL 值

java - JSF 标签未呈现 - FacesServlet 可能无法正常工作?

java - 将表单单选值发送到 Java servlet,返回 null

java - Spring 数据 findAll() 不急切获取

java - SQL 查询 1 :n relation, 查找具有两个匹配子项的所有实体

java - 在彼此之间类型转换子实体

java - JPA 连接运算符

java - 工作条件根据男性和女性的选择而匹配

可能缺少运行时依赖项的 Java 类

java - Jersey 2 + jackson 注释/@JsonIgnore