Matching a localized string in Hibernate - any best practice?

I am writing an application where all String properties should be localized, i.e. they must store a different value for each Locale available. A quick fix would be to use a map that can be easily displayed in Hibernate but not very pleasant for a Java programmer:

public class Product { private Map<Locale, String> description; private Map<Locale, String> note; 

Therefore, I implemented a LocalString object, which can contain different lines for different locales:

 public class LocalString { private Map<Locale, String> localStrings; 

The domain object becomes

 public class Product { private LocalString description; private LocalString note; 

What is the best way to map these objects to Hibernate annotations?

I think the best match will be done using LocalString as a component:

 @Embeddable public class LocalString { private Map<Locale, String> localStrings; @ElementCollection public Map<Locale, String> getLocalStrings() { return localStrings; } 

...

 @Entity public class Product { private Long id; private LocalString description; private LocalString note; @Embedded public LocalString getDescription() { return description; } 

Everything is in order: the hbm2ddl ant task creates two tables: the Products table and the Products_localStrings table, which contains the columns of keys and values. Everything breaks when I add getter for the second property:

  @Embedded public LocalString getNote() { return note; } 

The second property does not appear in the schema. I tried using the @AttributesOverride tag to define different names for two columns, but the generated schema is incorrect:

  @Embedded @AttributeOverrides({ @AttributeOverride(name="localStrings", column=@Column(name="description")) }) public LocalString getDescription() { return description; } @Embedded @AttributeOverrides({ @AttributeOverride(name="localStrings", column=@Column(name="note")) }) public LocalString getNote() { return note; } 

In the generated scheme, the key column has disappeared, and the primary key uses the "description", which is incorrect:

 create table Product_localStrings (Product_id bigint not null, note varchar(255), description varchar(255), primary key (Product_id, description)); 

Any way to fix this?

Would it be better to work without built-in components using LocalString as Entity?

Any alternative projects?

Thanks.

EDIT

I tried with xml mapping and I managed to get the correct schema, but the insert ended with a primary key violation because hibernate generates two inserts instead of one

 <hibernate-mapping> <class name="com.yr.babka37.demo.entity.Libro" table="LIBRO"> <id name="id" type="java.lang.Long"> <column name="ID" /> <generator class="org.hibernate.id.enhanced.SequenceStyleGenerator"/> </id> <property name="titolo" type="java.lang.String"> <column name="TITOLO" /> </property> <component name="descrizioni" class="com.yr.babka37.entity.LocalString"> <map name="localStrings" table="libro_strings" lazy="true" access="field"> <key> <column name="ID" /> </key> <map-key type="java.lang.String"></map-key> <element type="java.lang.String"> <column name="descrizione" /> </element> </map> </component> <component name="giudizi" class="com.yr.babka37.entity.LocalString"> <map name="localStrings" table="libro_strings" lazy="true" access="field"> <key> <column name="ID" /> </key> <map-key type="java.lang.String"></map-key> <element type="java.lang.String"> <column name="giudizio" /> </element> </map> </component> </class> </hibernate-mapping> 

Scheme

 create table LIBRO (ID bigint not null auto_increment, TITOLO varchar(255), primary key (ID)); create table libro_strings (ID bigint not null, descrizione varchar(255), idx varchar(255) not null, giudizio varchar(255), primary key (ID, idx)); alter table libro_strings add index FKF576CAC5BCDBA0A4 (ID), add constraint FKF576CAC5BCDBA0A4 foreign key (ID) references LIBRO (ID); 

Log:

 DEBUG org.hibernate.SQL - insert into libro_strings (ID, idx, descrizione) values (?, ?, ?) DEBUG org.hibernate.SQL - insert into libro_strings (ID, idx, giudizio) values (?, ?, ?) WARN ohutil.JDBCExceptionReporter - SQL Error: 1062, SQLState: 23000 ERROR ohutil.JDBCExceptionReporter - Duplicate entry '5-ita_ITA' for key 'PRIMARY' 

How can I say that hibernate generates only one insert, as shown below?

 insert into libro_strings (ID, idx, descrizione, giudizio) values (?, ?, ?, ?) 

EDIT on 2011.Apr.05

I have been using the Map solution for some time (annotated with @ElementCollection) until I come across two problems:

I know that there are many workarounds, such as using HQL instead of criteria and defining your own FieldBridge to take care of the map in Lucene, but I don't like the workarounds: they work until the following problem occurs. Therefore, I now adhere to this approach:

I define the "LocalString" class that contains the locale and value (Locale is actually ISO3 code):

 @MappedSuperclass public class LocalString { private long id; private String localeCode; private String value; 

Then I define, for each property that I want to localize, a subclass of LocalString that is empty:

 @Entity public class ProductName extends LocalString { // Just a placeholder to name the table } 

Now I can use it in my Product object:

 public class Product { private Map<String, ProductName> names; @OneToMany(cascade=CascadeType.ALL, orphanRemoval=true) @JoinColumn(name="Product_id") @MapKey(name="localeCode") protected Map<String, ProductName> getNames() { return names; } 

With this approach, I have to write an empty class for each property that I need to localize, which is necessary to create a unique table for this property. The advantage is that I can use criteria and search without restrictions.

+8
hibernate internationalization schema localization hibernate-mapping
source share
1 answer

Your xml mapping does not work because you have mapped the types of values ​​to the same table. When using an element or a composite element, data is processed as value types and refers to the class in which it is contained. This requires its own table. The identifier is unique only in the collection.

Or specify it as a component:

  <component name="descrizioni"> <map name="localStrings" table="descrizioni_strings" ...> <!-- ... --> </map> </component> <component name="giudizi"> <map name="localStrings" table="giudizi_strings" ... > <!-- ... --> </map> </component> 

Or as an independent entity:

  <many-to-one name="descrizioni" class="LocalString"/> <many-to-one name="giudizi" class="LocalString"/> 

In the second case, LocalString is an entity. This requires an identifier and a custom display definition.

+1
source share

All Articles