我们见过一些人使用国际化的参考数据,其中用户界面上显示的标签取决于用户的语言。在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实现标签缓存,则没有任何问题。如果您在自己的应用程序中实现它,则需要在数据更改时手动刷新缓存。
顺便说一下,这种模式不仅可以用于国际化!