【发布时间】:2014-06-06 15:35:19
【问题描述】:
我正在使用 JPA 编写一个 Java EE 应用程序,它需要国际化。
为此,我创建了一个名为MultilingualString 的类,它将Language 与String 关联起来
(因此扩展HashMap<Language, String>)如下:
/**
* This class is mapped to the database thanks
* to the Locale.toLanguageTag() method
* and Locale.fromLanguageTag(String) constructor
*/
public class Language {
public Locale locale;
/* Getters, Setters ... */
}
/**
* NB : This class extends HashMap to override the put method
* which needs to ignore putting null values instead of throwing
* a NullPointerException
*/
public class MultilingualString extends HashMap<Language, String> {
public Map<Language, String> getStrings() {
return (this);
}
/* ... */
}
现在,假设我们有一个 Entity,它的名称取决于 Language:
@Entity
@Access(AccessType.PROPERTY)
public class Entity {
private Long id;
private MultilingualString name;
@Id
public Long getId() { ... }
@Transient
public MultilingualString getName() { ... }
@ElementCollection
@MapKeyJoinColumn(name = "language_id")
public Map<Language, String> getStrings() {
return (this.name.getStrings());
}
/* ... */
}
此代码生成以下数据库架构:
Language :
id | bigint | NOT NULL | primary key
languageTag | character varying(255) | |
---------------------------------------------------------------------------------------------------
Entity :
id | bigint | NOT NULL | primary key
---------------------------------------------------------------------------------------------------
Entity_strings :
entity_id | bigint | | foreign key references Entity(id)
language_id | bigint | | foreign key references Language(id)
string | character varying(255) | |
这几乎是好的。
这个实现的问题是@Transient 在MultilingualString 字段上的重复
和 @ElementCollection @MapKeyJoinColumn 在 getStrings() 方法上(你必须每次都重写
您需要在数据库中映射一个MultilingualString BTW:每个实体类至少 1 个)。
在我看来,它使代码变得丑陋且难以维护。
另一件事是:Entity 通过Map 将Language 与String 相关联。
应该有一个将它们关联起来的类:当我阅读时说LocalizedString
here
所以我希望MultilingualString 表现得像一个值类型,所以
我不需要重复上面提到的那些多个注释并实现LocalizedString。
以下是我到目前为止的想法。它不起作用,GlassFish 无法启动
有了这样的映射,当 EclipseLink 看到 MultilingualString 映射时,我总是得到一个 java.util.NoSuchElementException。
@Embeddable
public class LocalizedString {
@ManyToOne
private Language language;
private String string;
}
@Embeddable
public class MultilingualString extends HashMap<Language, LocalizedString> {
/**
* Here I lost myself in all the available annotations
* and tried thousands of permutation, but always the same exception
* @MapKeyJoinColumn ?
* @MapKeyClass ?
* @CollectionTable ?
* ...
*/
@ElementCollection
public Map<Language, LocalizedString> getStrings() {
return (this);
}
}
@Entity
public class Entity {
@Id
private Long id;
@Embedded
private MultilingualString name;
}
因此,我希望通过此实现获得与上面所写相同(或最接近的)数据库映射。
这甚至可能吗?我读到Embeddable 类不应该包含另一个(集合)Embeddable 类,但在这里,我就是这种情况。
看了几篇文章和博客,并没有发现这么多像这样的情况(“JPA Map with Entity key and Embeddable value”)
实际上,我正在尝试做类似于this 的事情
除了我的地图值是Embeddable 类。
我找到了ONE similar situation
(除了我希望他的 Root 类是 Embeddable)在 StackOverflow 上,我测试了一个答案(没有运气),但它没有“标记为答案”并且问题没有太大成功.
【问题讨论】:
-
我认为不应修改 Entity 类以包含有关 i18n 的任何信息。您只需要在实体中保留键,并根据语言环境为其获取本地化消息并通过...据我所知,您正在将本地化内容持久化到数据库中?
-
@Vach 是的,但我需要:表单邀请用户添加一些
Entity实例,此表单询问用户名称是数据库中可用的语言。这些名称不是静态的。当然,对于静态标签,我不会将它们存储在数据库中,我有一些属性文件(messages_en、messages_fr 等),但我不能将它们用于此目的,因为我不知道字符串是什么会的。 -
好的,我有过这样的经历,这就是我所做的。我和你的情况几乎一样,用户应该输入本地化信息。起初我(就像你用实体存储它一样)那是可怕的。然后我们为定位信息保留了单独的实体,该实体保留了定位特定实体和与其相关的所有定位所需的信息。目标实体对其本地化一无所知。然后我们写了一个切面,这个切面把所有我们感兴趣的方法都返回本地化的值,并在执行后注入本地化的内容......
-
因此,使用特定语言环境登录的用户可以获得准确的本地化信息(如果没有可用的,则默认为英语)。我们缓存了这些方面的执行,一切都运行良好......我的观点是本地化是 AOP 的典型任务的“交叉关注”,尝试通过 OOP 实现它只会损害您的性能和代码可读性...... AOP 是一个真正必须了解的可编译语言(如 java)的技术,一旦你使用它,你会想知道到目前为止没有它你是怎么做的......
-
如果你需要,我可以给你那个方面的代码。您不仅可以通过这种方式解决本地化问题,还包括其安全性、事务管理、日志记录等......
标签: java jakarta-ee jpa orm eclipselink