- 译者序
- 前言
- 本书怎么使用
- 本书排版字体约定
- 本书网站
- 致谢
- 第一部分 Hibernate 快速入门
- 第 1 章 安装和设置
- 第 2 章 映射简介
- 第 3 章 驾驭 Hibernate
- 第 4 章 集合与关联
- 第 5 章 更复杂的关联
- 第 6 章 自定义值类型
- 第 7 章 映射标注
- 第 8 章 条件查询
- 第 9 章 浅谈 HQL
- 第二部分 与其他工具的集成
- 第 10 章 将 Hibernate 连接到 MySQL
- 第 11 章 Hibernate 与 Eclipse:Hibernate Tools 使用实战
- 第 12 章 Maven 进阶
- 第 13 章 Spring 入门:Hibernate 与 Spring
- 第 14 章 画龙点睛:用 Stripes 集成 Spring 和 Hibernate
- 附录 A Hibernate 类型
- 附录 B Criteria API
- 附录 C Hibernate SQL 方言
- 附录 D Spring 事务支持
- 附录 E 参考资源
- 作者简介
- 封面介绍
使用自定义的类型映射
如本章介绍部分所述,我们打算创建一个枚举类,而且可以用 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.
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论