Multilanguage string using JPA 2.0

Goal

Create a generic way to store multilanguage strings in your domain model using JPA 2.0

Description

You are working in a internationalized/multilanguage Java application, using JPA 2.0+ and your application must have contents which are, themselves, multilingue. Thus, all the relevant data should be saved in the database in a general way (to enforce reuse) for each of the selected languages.

How to

First of all, we will implement an abstract class (to be further extended with multilingue strings based on the size of the stored content, for instance), as follows (only the relevant code will be included)

@MappedSuperclass
public abstract class AbstractMultilanguageString implements Serializable {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Version
  private Integer version;

  public AbstractMultilanguageString() {
  }

  public AbstractMultilanguageString(String lang, String text) {
  }

  protected abstract Map<String, String> getMap();

  public void addText(String lang, String text) {
    getMap().put(lang, text);
  }
}

The previous class definition, mapped as a @MappedSuperclass is then extended in a way that the corresponding JPA mappings and column types may be defined. E.g.:

@Entity
@Table(name = "multilingue_string")
public class MultilanguageString extends AbstractMultilanguageString {

    private static final long serialVersionUID = 1L;

    @ElementCollection(fetch = FetchType.EAGER)
    @MapKeyColumn(name = "language", insertable = false, updatable = false)
    @CollectionTable(name = "multilingue_strings", joinColumns = @JoinColumn(name = "string_id"))
    @Column(name = "text")
    private Map<String, String> map = new HashMap<String, String>();

    public MultilanguageString() {
        super();
    }

    public MultilanguageString(final String language, final String text) {
        addText(language, text);
    }

    public Map<String, String> getMap() {
        return map;
    }
}

The previous class definition may then be used in client code that needs a (more or less short) String that is multilingue by nature (lets assume here the need for a project with a multilingue name). Something similar to the following snippet (assume that the class DomainObject is a mapped superclass defined in your project containing the structure for all your entities, namely the primary key and version):

@Entity
@Table(name = "project")
public class Project extends DomainObject {
  @OneToOne(fetch = FetchType.EAGER, orphanRemoval = true, cascade = CascadeType.ALL)
  @JoinColumn(name = "key_name")
  private MultilanguageString name;

  public void setName(final String name, final Language language) {
    name().addText(language.getShortName(), name);
  }

  public String getName(final Language language) {
    return name().getText(language.getShortName());
  }

  private MultilanguageString name() {
    return name != null ? name : (name = new MultilanguageString());
  }
  ...
}

The usage of our multilingue component is as simple as defining a one to one relation with that component in our entity class. Additionally, and to make our class more “developer” friendly (as in easier to use API), we add the methods to set and get the project’s name property.

Explanations

We have been using this implementation in JBoss 7.2 (which ships with Hibernate 4.2.0 that contains a lot of bug fixes regarding using element/map collections) successfully. The solution is a bit heavy as it fetches a lot of data from the multilingue string tables (each multilingue string is a relation with two tables – the table that contains the id of the string, and the table that contains the translations for that string id) but it is clean and uses JPA standards only, thus being theoretically (yes, I know that theory is not always what happens in real life!) usable in other containers/JPA providers.

Advertisements

One comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s