JPA 中的可本地化字符串
为了解决我的 Java EE 6 项目中对本地化动态(用户创建、存储在数据库中)数据的需求,我制作了一个通用本地化字符串表,它能够存储任何语言的任何字符串。这是为了避免创建大约 15 个额外的表,只包含内容的名称。我想知道两件事:
1)你认为这是一个好还是坏想法,为什么? 2)您知道针对缺点下列出的问题有任何干净的解决方案吗?
我的经验:
优点:只需要一张表,通用解决方案,很容易在 jpa 和数据库中配置。重复的代码是不存在的。
缺点:我发现的一个大问题是,由于 muiltilingual_string 表现在知道哪些对象正在使用它,因此级联删除无法在 SQL 中工作(但它可以在 JPA 中使用 orphanRemoval 工作)。如果从外部 JPA 工作,这会产生“死”字符串保留在数据库中的可能性。
实体:(AbstractEntity 是包含 @id 和 @version 的映射超类)
@Entity
@Table(schema = "COMPETENCE", name = "multilingual_string")
public class MultilingualString extends AbstractEntity{
@ElementCollection(fetch=FetchType.EAGER)
@MapKey(name = "language")
@CollectionTable(schema = "COMPETENCE", name = "multilingual_string_map",
joinColumns = @JoinColumn(name = "string_id"))
private Map<Language, LocalizedString> map = new HashMap<Language, LocalizedString>();
public MultilingualString() {}
public MultilingualString(Language lang, String text) {
addText(lang, text);
}
public void addText(Language lang, String text) {
map.put(lang, new LocalizedString(lang, text));
}
public String getText(Language lang) {
if (map.containsKey(lang)) {
return map.get(lang).getText();
}
return null;
}
public LocalizedString getLocalizedString(Language lang){
if(map.get(lang) == null)
map.put(lang, new LocalizedString(lang, null));
return map.get(lang);
}
@Override
public MultilingualString clone(){
MultilingualString ms = new MultilingualString();
for(LocalizedString s : map.values())
ms.addText(s.getLanguage(), s.getText());
return ms;
}
@Override
public String toString() {
return getId() == null ? "null " + this.getClass().getName() : getId().toString();
}
}
@Embeddable
public class LocalizedString {
@JoinColumn(name="lang_id")
private Language language;
@Column(name="text")
private String text;
public LocalizedString() {
}
public LocalizedString(Language language, String text) {
this.language = language;
this.text = text;
}
public Language getLanguage() {
return language;
}
public void setLanguage(Language language) {
this.language = language;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
这些类在其他实体中的使用方式如下:
@OneToOne(cascade=CascadeType.ALL, orphanRemoval=true)
@JoinColumn(name = "summary_stringid")
private MultilingualString summary;
表:
MULTILINGUAL_STRING (
id bigint primary key
);
MULTILINGUAL_STRING_MAP (
string_id bigint FOREIGN KEY REFERENCES MULTILINGUAL_STRING(id),
lang_id bigint FOREIGN KEY REFERENCES LANG(id),
text varchar(2000 char),
PRIMARY KEY(string_id, lang_id)
);
使用方式如下:
COMPETENCE (
id bigint PRIMARY KEY,
name_id bigint FOREIGN KEY REFERENCES MULTILINGUAL_STRING(id)
);
To solve the need for localized dynamic (user created, stored in db) data in my Java EE 6 project, I made a general localized string table that is capable of storing any string in any language. This to avoid having to make ~15 extra tables just containing names for stuff. What I would like to know is two things:
1) Do you think this is a good or bad idea, and why?
2) Do you know know of any clean solution to the issue listed under Cons?
My experiences:
Pros: Only one table needed, general solution that is easy to configure both in jpa and the db. Duplicated code is nonexistant.
Cons: The big issue I find is that due to the muiltilingual_string table now knowing what objects are using it, cascade deletes won't work from SQL (but it works in JPA with orphanRemoval). This produces the possibilities for "dead" strings to remain in the database if worked from outside JPA.
The Entities: (AbstractEntity is a mapped superclass containing @id and @version)
@Entity
@Table(schema = "COMPETENCE", name = "multilingual_string")
public class MultilingualString extends AbstractEntity{
@ElementCollection(fetch=FetchType.EAGER)
@MapKey(name = "language")
@CollectionTable(schema = "COMPETENCE", name = "multilingual_string_map",
joinColumns = @JoinColumn(name = "string_id"))
private Map<Language, LocalizedString> map = new HashMap<Language, LocalizedString>();
public MultilingualString() {}
public MultilingualString(Language lang, String text) {
addText(lang, text);
}
public void addText(Language lang, String text) {
map.put(lang, new LocalizedString(lang, text));
}
public String getText(Language lang) {
if (map.containsKey(lang)) {
return map.get(lang).getText();
}
return null;
}
public LocalizedString getLocalizedString(Language lang){
if(map.get(lang) == null)
map.put(lang, new LocalizedString(lang, null));
return map.get(lang);
}
@Override
public MultilingualString clone(){
MultilingualString ms = new MultilingualString();
for(LocalizedString s : map.values())
ms.addText(s.getLanguage(), s.getText());
return ms;
}
@Override
public String toString() {
return getId() == null ? "null " + this.getClass().getName() : getId().toString();
}
}
@Embeddable
public class LocalizedString {
@JoinColumn(name="lang_id")
private Language language;
@Column(name="text")
private String text;
public LocalizedString() {
}
public LocalizedString(Language language, String text) {
this.language = language;
this.text = text;
}
public Language getLanguage() {
return language;
}
public void setLanguage(Language language) {
this.language = language;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
The classes are used like following in other entities:
@OneToOne(cascade=CascadeType.ALL, orphanRemoval=true)
@JoinColumn(name = "summary_stringid")
private MultilingualString summary;
The tables:
MULTILINGUAL_STRING (
id bigint primary key
);
MULTILINGUAL_STRING_MAP (
string_id bigint FOREIGN KEY REFERENCES MULTILINGUAL_STRING(id),
lang_id bigint FOREIGN KEY REFERENCES LANG(id),
text varchar(2000 char),
PRIMARY KEY(string_id, lang_id)
);
Are used as following:
COMPETENCE (
id bigint PRIMARY KEY,
name_id bigint FOREIGN KEY REFERENCES MULTILINGUAL_STRING(id)
);
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我看到的一个问题是,这似乎不像 ResourceBundle 的级联那样工作。
在ResourceBundle中,如果您的语言环境是ES_es,它将首先在ES_es中查找本地化版本,如果没有找到,将在ES中尝试,如果没有,它将查找默认字符串(如果失败,它将显示# STRING_ID 字符串)。您可以有一个 .EN 字典、一个用于英国特有表达式的 .EN_uk 和一个用于美国特有表达式的 .EN_us,并且在 EN_uk 和 EN_us 中仅放置所需的键。
对于您的系统,它只查看当前区域设置,而不允许所有这些选项,因此您必须重新定义每个区域设置的所有值。因此,在上面的示例中,您必须为 EN_uk 和 EN_us 的每个键输入一个值。
One question I see is that this does not seem to work like the cascading of ResourceBundle.
In ResourceBundle, if your locale is ES_es, it will first look for the localized version in ES_es, if it does not find will try in ES and if not, it will look for the default String (in case it fails it will show a #STRING_ID String). You could have a .EN dictionary, a .EN_uk for expressions particular to the United Kingdom, and a .EN_us for expressions particular to the US, and in EN_uk and EN_us put only the keys needed.
With your system, it just looks at the current locale without allowing all of these options, so you have to redefine all the values for each locales. So, in the above example, you would have to put a value for each key both for EN_uk and EN_us.
这种方法确实有意义,而且并不新鲜。
它已在此处针对不同的 JPA 提供商进行发布、讨论和测试:
http://hwellmann.blogspot.com/2010/07/jpa -20-mapping-map.html
(我相信你从那里获取了代码)
Such approach does make sense and it is not new.
It was published, discussed and tested against different JPA providers here:
http://hwellmann.blogspot.com/2010/07/jpa-20-mapping-map.html
(I believe you took code from there)