我们见过一些人使用国际化的参考数据,其中用户界面上显示的标签取决于用户的语言。在Hibernate中处理这个问题并不明显,我一直想写一下我首选的解决方案。
假设我有一个表,它定义了标签,包括一个唯一的代码和一种语言。
create table Label (
code bigint not null,
language char(2) not null,
description varchar(100) not null,
primary key(code, langauge)
)
其他实体通过代码引用标签。例如,类别表需要类别描述。
create table Category (
category_id bigint not null primary key,
discription_code bigint not null,
parent_category_id foreign key references(category)
)
请注意,对于每个描述代码,在标签表中可能存在多个匹配的行。在运行时,我的Java Category实例应加载用户语言偏好的正确描述。
UI标签当然应在事务之间进行缓存。我们可以在我们的应用程序中实现此缓存,或者通过映射一个Label类并使用Hibernate的第二级缓存来实现。我们如何实现这并不很重要,我们假设我们有一些缓存,并可以使用以下方式检索描述
Label.getDescription(code, language)
并使用以下方式获取代码
Label.getCode(description, language)
我们的Category类如下所示
public class Category {
private Long id;
private String description;
private Category parent;
...
}
描述字段持有用户语言中类别的字符串描述。但在数据库表中,我们只有描述的代码。这似乎不能用Hibernate映射来处理这种情况。
当你在Hibernate中似乎不能做某事时,你应该想想 UserType
!我们将使用UserType来解决这个问题。
public class LabelUserType {
public int[] sqlTypes() { return Types.BIGINT; }
public Class returnedClass() { return String.class; }
public boolean equals(Object x, Object y) throws HibernateException {
return x==null ? y==null : x.equals(y);
}
public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
throws HibernateException, SQLException {
Long code = (Long) Hibernate.LONG.nullSafeGet(rs, names, owner);
return Label.getDescrption( code, User.current().getLanguage() );
}
public void nullSafeSet(PreparedStatement st, Object value, int index)
throws HibernateException, SQLException {
Long code = Label.getCode( (String) value, User.current().getLanguage() );
Hibernate.LONG.nullSafeSet(st, code, index);
}
public Object deepCopy(Object value) throws HibernateException {
return value; //strings are immutable
}
public boolean isMutable() {
return false;
}
}
(我们可以通过调用User.current().getLanguage()来获取当前用户的语言偏好。)
现在我们可以映射Category类
<class name="Categoy">
<id name="id" column="category_id">
<generator class="native"/>
</id>
<property
name="description"
type="LabelUserType"
column="discription_code"
not-null="true"/>
<many-to-one
name="parent"
column="parent_category_id"/>
</class>
请注意,我们甚至可以对Category.description编写查询。例如
String description = ...;
session.createQuery("from Category c where c.description = :description")
.setParameter("description", description, Hibernate.custom(LabelUserType.class))
.list();
或者,指定代码
Long code = ...;
session.createQuery("from Category c where c.description = :code")
.setLong("description", code)
.list();
不幸的是,我们无法使用like进行基于文本的搜索,也无法根据文本描述进行排序。我们需要在内存中对标签进行排序。
请注意,这种实现非常高效,我们在查询中从不需要连接到标签表 - 我们根本不需要查询该表,除了在启动时初始化缓存。一个潜在的问题是如果标签数据发生变化,则需要保持缓存更新。如果您使用Hibernate实现标签缓存,则没有任何问题。如果您在自己的应用程序中实现它,则需要在数据更改时手动刷新缓存。
顺便说一下,这种模式不仅可以用于国际化!