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)
);