开发者

Localizable strings in JPA

开发者 https://www.devze.com 2023-03-05 07:13 出处:网络
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 t

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 = "multilingua开发者_如何转开发l_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)
);


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.


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)

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号