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 {
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.