开发者创建属于他们自己的值类型也是很容易的。比如说,你可能希望持久化java.lang.BigInteger类型的属性,持久化成为VARCHAR字段。Hibernate没有内置这样一种类型。自定义类型能够映射一个属性(或集合元素)到不止一个数据库表字段。比如说,你可能有这样的Java属性:getName()/setName(),这是java.lang.String类型的,对应的持久化到三个字段:FIRST_NAME, INITIAL, SURNAME。
<column name="first_string"/>
<column name="second_string"/>
</property>
注意使用<column>标签来把一个属性映射到多个字段的做法。
<type name="com.mycompany.usertypes.DefaultValueIntegerType">
<param name="default">0</param>
</type>
</property>
现在,UserType 可以从传入的Properties对象中得到default 参数的值。
<param name="default">0</param>
</typedef>
<property name="priority" type="default_zero"/>
也可以根据具体案例通过属性映射中的类型参数覆盖在typedef中提供的参数。
尽管 Hibernate 内建的丰富的类型和对组件的支持意味着你可能很少 需要使用自定义类型。不过,为那些在你的应用中经常出现的(非实体)类使用自定义类型也是一个好方法。例如,一个MonetaryAmount类使用CompositeUserType来映射是不错的选择,虽然他可以很容易地被映射成组件。这样做的动机之一是抽象。使用自定义类型,以后假若你改变表示金额的方法时,它可以保证映射文件不需要修改。
--------------------------------------------------------------------------
Hibernate 提供了客户化映射类型接口,允许用户以编程的方式创建自定义的映射类型,以便把持久化类的任意类型的属性映射到数据库中.例1的PhoneUserType实现了net.sf.hibernate.UserType接口,它能够把Customer类的Integer类型的phone属性映射到CUSTOMER表的VARCHAR类型的PHONE字段.
例1:
package mypack;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import net.sf.hibernate.Hibernate;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.UserType;
/**
* @author lfm
*
*/
public class PhoneUserType implements UserType {
private static final int[] SQL_TYPES = {Types.VARCHAR};
public int[] sqlTypes() {
// TODO 自动生成方法存根
return SQL_TYPES;
}
public Class returnedClass() {
// TODO 自动生成方法存根
return Integer.class;
}
public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner) throws HibernateException, SQLException {
// TODO 自动生成方法存根
if(resultSet.wasNull())
return null;
String phone = resultSet.getString(names[0]);
return new Integer(phone);
}
public void nullSafeSet(PreparedStatement statement, Object value, int index) throws HibernateException, SQLException {
// TODO 自动生成方法存根
if(value== null)
arg0.setNull(index, Types.VARCHAR);
else {
String phone = ((Integer)value).toString();
statement.setString(index, phone);
}
}
public Object deepCopy(Object value) throws HibernateException {
// TODO 自动生成方法存根
return value;
}
public boolean equals(Object x, Object y) throws HibernateException {
// TODO 自动生成方法存根
if(x== y)
return true;
if(x== null || y== null)
return false;
return x.equals(y);
}
public boolean isMutable() {
// TODO 自动生成方法存根
return false;
}
}
PhoneUserType实现了net.sf.hibernate.UserType接口中的所有方法,下面解释这些方法的作用.
(1)设置CUSTOMERS表的PHONE字段SQL类型,它是VARCHAR类型:
private static final int[] SQL_TYPES = {Types.VARCHAR};
public int[] sqlTypes() {return SQL_TYPES;}
(2)设置Customer类的phone属性的Java类型,它是Integer类型:
public CLass returnedClass() {return Integer.class;}
(3)Hibernate调用isMutable()方法来了解Integer类是否是可变类.本例的Integer类是不可变类,因此返回false.Hibernate处理不可变属性类型的属性时,会采取一些性能优化措施.
public boolean isMutable() {return false;}
(4)Hibernate调用deepCopy(Object value)方法来生成phone属性的快照.deepCopy()方法的value参数代表Integer类型的phone属性.由于Integer类是不可变类,因此本方法直接返回参数:
public Object deepCopy(Object value) {
return value;
}
对于可变类,必须返回参数的拷贝值.后面会说.
(5)Hibernate调用equals(Object x, Object y)方法类比较Customer类的phone属性的当前值是否和它的快照相同.该方法的一个参数代表phone属性的当前值,一个参数代表由deepCopy()方法生成的phone属性的快照:
public boolean equals(Object x, Object y) {
if(x== y)
return true;
if(x== null || y== null)
return false;
return x.equals(y);
}
(6)当Hibernate从数据库加载Customer对象时,调用nullSafeGet()方法类取得phone属性值.参数resultSet为JDBC查询结果集,参数names为存放了表字段名的数组,此处为{"PHONE"},参数owner代表phone属性的宿主,此处为Customer对象.
public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner) throws HibernateException,SQLException {
if(resultSet.wasNull()) return null;
String phone = resultSet.getString(names[0]);
return new Integer(phone);
}
在nullSafeGet方法中,先从ResultSet从读取PHONE字段的值,然后把它转换为Integer对象,最后将它作为phone属性值返回.
(7)当Hibernate把Customer对象持久化到数据库中时,调用nullSafeSet()方法来把phone属性添加到INSERT SQL语句中.statement参数包含了预定义的INSERT SQL语句,参数value代表phone属性,参数index代表把phone属性插入到INSERT SQL语句中的位置:
public void nullSafeSet(PreparedStatement statement, Object value, int index) throws HibernateException, SQLException {
if(value == null) {
statement.setNull(index, Types.VARCHAR);
}else{
String phone = ((Integer)value).toString();
statement.setString(index, phone);
}
}
在nullSafeSet()方法中,参数value代表phone属性.因此,先把Integer类型的value转换为String类型,然后把它添加到JDBC Statement中.
定义了PhoneUserType类后,在Customer.hbm.xml中按如下方式映射Customer类的phone属性:
<property name="name" type="mypack.PhoneUserType">
<column name="phone" length="8"/>
</property>
PhoneUserType不仅仅可以用来映射phone属性,事实上,它能够把持久化类的任何一个Integer类型的属性映射到数据库中VARCHAR类型的字段.
<typedef class="com.mycompany.usertypes.DefaultValueIntegerType" name="default_zero">
如果你非常频繁地使用某一UserType,可以为他定义一个简称。这可以通过使用 <typedef>元素来实现。Typedefs为一自定义类型赋予一个名称,并且如果此类型是参数化的,还可以包含一系列默认的参数值。
<property name="priority">
你甚至可以在一个映射文件中提供参数给一个UserType。 为了这样做,你的UserType必须实现org.hibernate.usertype.ParameterizedType接口。为了给自定义类型提供参数,你可以在映射文件中使用<type>元素。
CompositeUserType, EnhancedUserType, UserCollectionType, 和 UserVersionType 接口为更特殊的使用方式提供支持。
<property name="twoStrings" type="org.hibernate.test.DoubleStringType">
要实现一个自定义类型,可以实现org.hibernate.UserType或org.hibernate.CompositeUserType中的任一个,并且使用类型的Java全限定类名来定义属性。请查看org.hibernate.test.DoubleStringType这个例子,看看它是怎么做的。