Hibernate中的国际化数据

发布者    |      

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

顺便说一下,这种模式不仅可以用于国际化!


返回顶部