返回介绍

使用自定义的类型映射

发布于 2025-04-21 21:42:13 字数 8723 浏览 0 评论 0 收藏

如本章介绍部分所述,我们打算创建一个枚举类,而且可以用 Hibernate 对它的值进行持久化。我们将这个新类命名为 SourceMediaType。接下来再决定它需要实现 UserType 接口,还是 CompositeUserType 接口。Hibernate 参考文档对此问题没有提供什么指导,但 API 文档对这两个接口名称所隐含的意义做了确认:只有在自定义类的实现想以命名属性的形式来暴露其内部结构,使其能在查询中被单独访问时(例如 Z I P 码的例子),才需要使用 CompositeUserType 接口。对于 SourceMedia,实现简单的 UserType 就够用了。例 6-2 演示了符合我们需要的映射管理器( [1] 1)(mapping manager)的源代码。

例 6-2:自定义类型映射处理器(SourceMediaType.java)

package com.oreilly.hh;

import java.io.Serializable;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Types;

import org.hibernate.Hibernate;

import org.hibernate.HibernateException;

import org.hibernate.usertype.UserType;

/**

*Manages persistence for the{@link SourceMedia}typesafe enumeration.

*/

public class SourceMediaType implements UserType{

/**

*Indicates whether objects managed by this type are mutable.

*

*@return<code>false</code>,since enumeration instances are immutable

*singletons.

*/

public boolean isMutable(){

return false;

}

/**

*Return a deep copy of the persistent state, stopping at

*entities and collections.

*

*@param value the object whose state is to be copied.

*@return the same object, since enumeration instances are singletons.

*/

public Object deepCopy(Object value){

return value;

}

/**

*Compare two instances of the class mapped by this type for persistence

*"equality".

*

*@param x first object to be compared.

*@param y second object to be compared.

*@return<code>true</code>iff both represent the same SourceMedia type.

*@throws ClassCastException if x or y isn't a{@link SourceMedia}.

*/

public boolean equals(Object x, Object y){

//We can compare instances, since SourceMedia are immutable singletons

return(x==y);

}

/**

*Determine the class that is returned by{@link#nullSafeGet}.

*

*@return{@link SourceMedia},the actual type returned

*by{@link#nullSafeGet}.

*/

public Class returnedClass(){

return SourceMedia.class;

}

/**

*Determine the SQL type(s)of the column(s)used by this type mapping.

*

*@return a single VARCHAR column.

*/

public int[]sqlTypes(){❶

//Allocate a new array each time to protect against callers changing

//its contents.

int[]typeList=new int[1];

typeList[0]=Types.VARCHAR;

return typeList;

}

/**

*Retrieve an instance of the mapped class from a JDBC{@link ResultSet}.

*

*@param rs the results from which the instance should be retrieved.

*@param names the columns from which the instance should be retrieved.

*@param owner the entity containing the value being retrieved.

*@return the retrieved{@link SourceMedia}value, or<code>null</code>.

*@throws SQLException if there is a problem accessing the database.

*/

public Object nullSafeGet(ResultSet rs, String[]names, Object owner)

throws SQLException❷

{

//Start by looking up the value name

String name=(String)Hibernate.STRING.nullSafeGet(rs, names[0]);

if(name==null){

return null;

}

//Then find the corresponding enumeration value

try{

return SourceMedia.valueOf(name);❸

}

catch(IllegalArgumentException e){

throw new HibernateException("Bad SourceMedia value:"+name, e);❹

}

}

/**

*Write an instance of the mapped class to a{@link PreparedStatement},

*handling null values.

*

*@param st a JDBC prepared statement.

*@param value the SourceMedia value to write.

*@param index the parameter index within the prepared statement at which

*

this value is to be written.

*@throws SQLException if there is a problem accessing the database.

*/

public void nullSafeSet(PreparedStatement st, Object value, int index)

throws SQLException❺

{

String name=null;

if(value!=null)

name=((SourceMedia)value).toString();

Hibernate.STRING.nullSafeSet(st, name, index);

}

/**

*Reconstruct an object from the cacheable representation.At the very least this

*method should perform a deep copy if the type is mutable.(optional operation)

*

*@param cached the object to be cached

*@param owner the owner of the cached object

*@return a reconstructed object from the cachable representation

*/

public Object assemble(Serializable cached, Object owner){

return cached;

}

/**

*Transform the object into its cacheable representation.At the very least this

*method should perform a deep copy if the type is mutable.That may not be enough

*for some implementations, however;for example, associations must be cached as

*identifier values.(optional operation)

*

*@param value the object to be cached

*@return a cachable representation of the object

*/

public Serializable disassemble(Object value){

return(Serializable)value;

}

/**

*Get a hashcode for an instance, consistent with persistence"equality".

*@param x the instance whose hashcode is desired.

*/

public int hashCode(Object x){

return x.hashCode();

}

/**

*During merge, replace the existing(target)value in the entity we are merging to

*with a new(original)value from the detached entity we are merging.For immutable

*objects, or null values, it is safe to simply return the first parameter.For

*mutable objects, it is safe to return a copy of the first parameter.For objects

*with component values, it might make sense to recursively replace component values.

*

*@param original the value from the detached entity being merged

*@param target the value in the managed entity

*@return the value to be merged

*/

public Object replace(Object original, Object target, Object owner)

throws HibernateException

{

return original;

}

}

这么长的一大段代码看起来有些令人畏惧,不过,不要担心。这个类中的所有方法都是由 UserType 接口指定的。我们的实现相当简洁明了,刚好符合我们所做的简单映射的需要。前三个方法没什么可讨论的,看看 JavaDoc 和代码内嵌的注释就够了,以下是对代码中一些有意思的地方进行的注释:

❶sqlTypes()方法向 Hibernate 报告该自定义类型需要保存其值的列的数量,以及这些列的 SQL 类型。这里我们的类型使用一个 VARCHAR 类型的列。

因为 API 规定必须将这种信息作为数组返回,安全的编码实践要求在每次调用时都创建和返回一个新的数组,以防止恶意代码或错误代码可能操纵该数组的内容(Java 不支持不可变数组。如果 UserType 接口声明这个方法返回 Collection 或 List 就好了,因为这些类型可以是不可变的)。

❷在 nullSateGet()中,我们将数据结果转换为相应的 MediaSource 枚举值。因为我们知道这个值在数据库中保存为字符串,所以能够将实际的查询过程委托给 Hibernate 的工具方法,让它从数据库结果中加载字符串。在大多数情况下,都能够做这样的处理。

❸接着只需要使用枚举类型自己的实例查询功能。

❹HibernateException 是当执行映射操作发生问题时,Hibernate 抛出的一个 Runtime-Exception。我们这里“借用”了现成的 Hibernate 异常,因为可以认为问题与映射相关。如果我们想搞得花哨些,可以通过定义自己的异常类型来提供更多的细节,不过最好让自定义的异常类扩展 HibernateException,尤其是在像 Spring 这样的抽象框架中使用时,这样做更有意义(我们将在第 13 章介绍 Spring)。

❺另一个方向的映射是由 nullSafeSet()处理的。我们再一次依靠 Java 与内建的 enum 机制将 SourceMedia 实例转换为相应的名称,接着使用 Hibernate 工具将这个字符串保存到数据库中。

在所有对值进行处理的方法中,很重要的一点就是编写代码的方法,如果任何一个参数为 null 时(这种情况经常发生),方法执行不会崩溃。方法名称前的"nullSafe"前缀就是对此的一种提醒,不过,即使是 equals()方法也必须谨慎使用。盲目地使用 x.equals(y),当 x 为 null 时,就会让程序崩溃。

其他方法是一些普通的接口方法的实现,因为在处理不可变的单例对象(immutable singleton)时,虽然是枚举值,也应该避免在持久化管理时潜在的复杂性。

好吧,我们已经创建了一个自定义的类型持久化处理器,它并没有想象中的那么难。接下来就可以真正用它来持久化我们的枚举数据了。

应该怎么做

这实在是简单到令人尴尬。在有了值类、SourceMedia、持久化管理器和 SourceMediaType 以后,需要做的就是修改以前的映射文档,在需要映射的地方,使用我们自定义的持久化管理器类而不是原先的原始值类型(raw value type)。

我们将通过一个例子,用媒体来源枚举类型来演示它的持久化。但是在深入这个例子以前,我们先稍候片刻,考虑一下如何让实现变得更加通用,以便在更大的项目中使用。

其他

如果需要持久化保存多个枚举类型呢?如果你曾经考虑过这个问题,你可能会认为这与例 6-2 在本质上没有什么不同,只不过例 6-2 只专注于持久化我们的 SourceMedia 枚举类型。代码中涉及类型的位置只有几处(如果忽略 JavaDoc,甚至要更少)。为什么不能将枚举类型参数化,在一个单独的实现中就可以支持任何枚举类型,这样做不是更简单吗?

确实,这样做会相当简单,如果 Hibernate 要是内建了一种这样简单而灵活的机制,那就更好了。在 Hibernate 维基(wiki)上还有几种可供选择的方法(在几个不同的页面上,可能需要按主题类型浏览),或许我们应该采用 Gavin King 提出的 Enhanced UserType(改进版的 UserType)(在 Java 5 EnumUserType( [2] )主题页),来作为我们非官方的“官方选择”(afficial choice),因为他是 Hibernate 诸多作者之一。这种方法的功能看起来已经相当完整了,只是还没有轻率地付诸实用。不过,现在至少可以考虑一下将它与例 6-2 进行比较,看看在让我们的解决方案更为通用化方面还能做哪些工作。

注意:我可以听到很多感慨“到时间了”!

但是现在,继续我们具体的示例,看看怎么样真正使用我们的枚举类型!

[1] 这里,原文中使用了映射管理器(mapping manager)以及自定义类型映射处理器(custom type mapping handler)这两个不同的词,容易让读者产生混淆。事实上,它们都是指同样的概念。 [2] http://www.hibernate.org/272.html.

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。