【发布时间】:2026-02-01 12:35:02
【问题描述】:
我正在编写一个应用程序,其中所有 String 属性都必须本地化,即它们必须为每个可用的 Locale 存储不同的值。 一个快速的解决方案是使用 Map,它可以在 Hibernate 中轻松映射,但对 Java 程序员来说不是很好:
public class Product {
private Map<Locale, String> description;
private Map<Locale, String> note;
因此我实现了一个 LocalString 对象,它可以为不同的语言环境保存不同的字符串:
public class LocalString {
private Map<Locale, String> localStrings;
领域对象变成
public class Product {
private LocalString description;
private LocalString note;
如何最好地使用 Hibernate 注释映射这些对象?
我认为最好的映射是使用 LocalString 作为一个组件:
@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;
}
到目前为止一切都很好:hbm2ddl ant 任务创建了两个表,一个“Products”表和一个包含键和值列的“Products_localStrings”表。 当我为第二个属性添加 getter 时,一切都会中断:
@Embedded
public LocalString getNote() {
return note;
}
第二个属性未显示在架构中。 我尝试使用@AttributesOverride 标记为两列定义不同的名称,但生成的架构不正确:
@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;
}
在生成的schema中,key列已经消失,主键使用“description”,不正确:
create table Product_localStrings (Product_id bigint not null, note varchar(255), description varchar(255), primary key (Product_id, description));
有什么办法解决这个问题?
如果没有嵌入式组件,使用 LocalString 作为实体会更好吗?
还有其他的设计吗?
谢谢。
编辑
我尝试了一个 xml 映射并且我设法获得了一个正确的模式,但是由于 hibernate 生成了两个插入而不是一个,因此插入失败并导致主键冲突
<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>
架构是
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);
日志:
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 o.h.util.JDBCExceptionReporter - SQL Error: 1062, SQLState: 23000
ERROR o.h.util.JDBCExceptionReporter - Duplicate entry '5-ita_ITA' for key 'PRIMARY'
我如何告诉 hibernate 只生成一个插入,如下所示?
insert into libro_strings (ID, idx, descrizione, giudizio) values (?, ?, ?, ?)
于 2011 年 4 月 5 日编辑
我一直在使用 Map 解决方案(使用 @ElementCollection 注释),直到我偶然发现了两个问题:
- 当前的 Criteria API 不适用于嵌入式集合:http://opensource.atlassian.com/projects/hibernate/browse/HHH-3646
- 当前的 Hibernate Search (Lucene) API 也不适用于嵌入式集合:http://opensource.atlassian.com/projects/hibernate/browse/HSEARCH-566
我知道有很多变通方法,例如使用 HQL 代替 Criteria 并定义您自己的 FieldBridge 来处理 Lucene 中的地图,但我不喜欢变通方法:它们在下一个问题出现之前一直有效。 所以我现在采用这种方法:
我定义了一个包含语言环境和值的类“LocalString”(语言环境实际上是 ISO3 代码):
@MappedSuperclass
public class LocalString {
private long id;
private String localeCode;
private String value;
然后我为每个要本地化的属性定义一个 LocalString 的子类,它是空的:
@Entity
public class ProductName extends LocalString {
// Just a placeholder to name the table
}
现在我可以在我的 Product 对象中使用它:
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;
}
使用这种方法,我必须为需要本地化的每个属性编写一个空类,这是为该属性创建唯一表所必需的。 好处是我可以不受限制地使用 Criteria 和 Search。
【问题讨论】:
-
进一步的实验表明 AttributeOverrides 将映射的键和值列折叠成一个列。
-
我尝试了一个 xml 映射并且我设法获得了一个有效的模式,但是 Hibernate 生成的 sql 将每个属性存储在不同的插入中,以违反主键约束而告终。
-
老实说,这比我有时间阅读的要多得多,我认为其中包含多个问题。当您可以将问题简化为一个易于描述的问题时,您将获得更好的支持。
-
问题还是一样的:“如何在Hibernate中映射一个本地化的字符串”。我展示了我所有的尝试。对不起,如果它太冗长,但我做了很多尝试......我认为这是很多人必须面对的问题,我很惊讶没有最佳实践(我在谷歌上找不到太多)。 2011 年 4 月 5 日编辑的版本仍然让我不满意。
-
另外,需要为每个本地化属性编写一个特定的类是应该进行“优化”的东西。我的这个优化版本是使用一个公共的超类和一个空的子类,但是有一个空的子类只是为了命名表对我来说似乎是一个 hack,所以也许有更好的解决方案,或者这是一个值得研究的领域:a方法级别的新 @Table 注释可能是最好的解决方案,我不知道。
标签: hibernate localization internationalization schema hibernate-mapping