How to internationalize a Hibernate object

I am trying to add support for java objects for internationalization (multiple languages). I am open to any options with a minimum number of code templates when adding translations to each new field. I'm not limited to JPA, I can also use hibernation annotations. In the worst case, simple sql will do the trick too. Perhaps there are some ready-made libraries that I have not found. You should not follow my idea described below.

Ideally, I need a database to look like this:

i18n +------+--------+------+ | id | locale | text | +------+--------+------+ | 1 | en | foo | +------+--------+------+ | 1 | de | bar | +------+--------+------+ | 2 | en | foo2 | +------+--------+------+ | 2 | de | bar2 | +------+--------+------+ parent +------+------+ | id | text | +------+------+ | 99 | 1 | +------+------+ | 100 | 2 | +------+------+ 

i18n is a table that should contain only 3 columns: id , locale and text . The parent table has a text column (if there is only one field that requires i18n, more columns) containing the values ​​from i18n.id I tried the following mapping in the Parent class:

 @ElementCollection @CollectionTable(name="i18n", joinColumns = @JoinColumn(referencedColumnName="id")) @MapKeyColumn(name="locale") @Column(name="text") public Map<String, String> text = newHashMap(); 

It seems to work when DDL generation is disabled, and I create the tables myself, but when DDL generation is turned on, it generates an unnecessary i18n.parent_id column and a restriction for it:

 ALTER TABLE PUBLIC.I18N ADD CONSTRAINT PUBLIC.FK_HVGN9UJ4DJOFGLT8L78BYQ75I FOREIGN KEY(PARENT_ID) INDEX PUBLIC.FK_HVGN9UJ4DJOFGLT8L78BYQ75I_INDEX_2 REFERENCES PUBLIC.PARENT(ID) NOCHECK 

How can I get rid of this extra column? Is it possible to avoid a link from table i18n to table parent ? This link makes it difficult to reuse table i18n . I need to either hold some discriminator value in the i18n table, or use the GUID in the entire database, as the identifier in different tables will conflict. The first option means a lot of template code. The second option means a lot of work that needs to be done in the current project.

I need to reuse i18n for an entity. My parent classes will look something like this. And there will be several such parent classes with a different set of fields that need to be internationalized.

 @Entity public class Parent { @Id @GeneratedValue public Long id; public String title; // must be in internationalized public String text; // must be in internationalized public String details; // must be in internationalized // ... other fields } 
+8
java hibernate jpa internationalization
source share
4 answers

In database collection instances, the foreign key of the object to which the collection belongs is allocated. This foreign key is called a collection key column or collection table columns.

So, I assume that you want to disable key generation for your proposal, you can just do it with this

 @Entity public class Parent { @Id @GeneratedValue public Long id; @ElementCollection @CollectionTable(name = "i18n", foreignKey = @ForeignKey(name = "none"), joinColumns = @JoinColumn(name = "id")) @MapKeyColumn(name = "locale") @Column(name = "text") public Map<String, String> text = new HashMap<>(); public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Map<String, String> getText() { return text; } public void setText(Map<String, String> text) { this.text = text; } } 

Then, when you check the generated DDL, the forgiveness key does not apply to table I18N, but still get the opportunity

 @Test public void testParent() { Parent p = new Parent(); HashMap<String, String> text = new HashMap<>(); text.put("en", "foo"); text.put("de", "bar"); p.setText(text); entityManager.persist(p); entityManager.flush(); Parent parent = entityManager.find(Parent.class, p.getId()); System.out.println("en: " + parent.getText().get("en")); System.out.println("de: " + parent.getText().get("de")); } 

About the program - a simple test (Spring Boot 1.4 release), and he will see the output in the console:

 Hibernate: create table i18n ( id bigint not null, text varchar(255), locale varchar(255) not null, primary key (id, locale) ) Hibernate: create table parent ( id bigint generated by default as identity, primary key (id) ) ...... Hibernate: insert into parent (id) values (null) Hibernate: insert into i18n (id, locale, text) values (?, ?, ?) Hibernate: insert into i18n (id, locale, text) values (?, ?, ?) en: foo de: bar 

This is the table in H2 db:

enter image description here

+2
source share

Do not specify @JoinColumn incorrectly using

 @JoinColumn(referencedColumnName="id")) 

but not

 @JoinColumn(name="id")) 

http://docs.oracle.com/javaee/6/api/javax/persistence/JoinColumn.html#referencedColumnName ()

When used in a CollectionTable mapping, the referenced column is the table of the object containing the collection

So, essentially, you (re) define the identifier column in the parent, and Hibernate generates the FK column in i18n using the default naming strategy.

Using this:

 @Entity @Table(name = "parent") public class Parent { @Id private Long id; @ElementCollection @CollectionTable(name = "i18n", foreignKey = @ForeignKey(name = "ii8n_to_parent_fk"), joinColumns = @JoinColumn(name = "id")) @MapKeyColumn(name = "locale") @Column(name = "text") public Map<String, String> text = new HashMap<>(); } 

generates the following:

 19:29:08.890 [main] DEBUG jdbc.sqlonly - org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:51) 3. create table i18n (id bigint not null, text varchar(255), locale varchar(255) not null, primary key (id, locale)) 19:29:09.464 [main] DEBUG jdbc.sqlonly - org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:51) 3. alter table i18n add constraint ii8n_to_parent_fk foreign key (id) references parent 
0
source share

Hibernate doesn't seem to provide i18n support out of the box, so you're on the right track to implement a custom solution for this. I can also assume that your goal is to add localization support to an existing project with minimal cost.

I can suggest you use the ManyToMany relation in the Parent and i18n tables.

In this case, you are completely independent of the structure of the Parent and i18n tables as you want, but there is some overhead with additional join tables for each "Parent" that contains pairs of PK i18n and "Parent" table links. Also with the ManyToMany form, ManyToMany approach records can be reused in different "Parent" tables.

So your table structure might look like this:

 i18n +------+--------+------+ | id | locale | text | +------+--------+------+ | 1 | en | foo | +------+--------+------+ | 2 | de | bar | +------+--------+------+ | 3 | en | foo2 | +------+--------+------+ | 4 | de | bar2 | +------+--------+------+ i18n_parent +-------------+---------+ | text_id | i18n_id | +-------------+---------+ | 1 | 1 | +------+------+---------+ | 1 | 2 | +------+------+---------+ | 2 | 3 | +------+------+---------+ | 2 | 4 | +------+------+---------+ parent +------+------+ | id | text | +------+------+ | 99 | 1 | +------+------+ | 100 | 2 | +------+------+ 

Example entity code:

 @Entity public class Parent { @Id @GeneratedValue public Long id; @ManyToMany @JoinTable(name = "i18n_parent", joinColumns = @JoinColumn(name = "text_id"), inverseJoinColumns = @JoinColumn(name = "i18n_id")) @MapKey(name = "locale") private Map<String, LocalizedTextEntity> text = new HashMap<>(); ..... } @Entity @Table(name = "i18n") public class LocalizedTextEntity { @Id @GeneratedValue public Long id; @Column(name = "locale") private String locale; @Column(name = "text") private String text; ..... } 
0
source share

It looks like you are looking for a sleeping composite primary key. You must complete this trick:

 @Embeddable public class LocaleForeignKey { Integer id; String locale; } @Entity public class I18n { @AttributeOverrides({ @AttributeOverride(name="id", column = @Column(name="id")) @AttributeOverride(name="locale", column = @Column(name="locale")) }) @EmbeddedId LocaleForeignKey id; String text; ...getters-setters } 

Unfortunately, I have no idea how to map it as a locale map, but think that this is possible with @JoinColumn annotations or try going to the @Alan Hay post.

0
source share

All Articles