使用UserType首先要弄清楚它的目的。大家知道Hibernate解决的主要是对象数据库阻抗失衡的问题,也就是如何将一个或多个对象保存到一个或多个数据库表格中。这其中有很多方法,其实大部分情况下采用@Embeddable和 @Embedded 就可以解决问题了,只有嵌入对象方式无法满足要求时,或者是Hibernate默认的持久化方式无法满足要求时,才应该考虑UserType。总之记住一 个原则,不到山穷水尽,不要轻易使用UserType。还有一个要慎重考虑使用UserType的原因是:一旦采用了UserType,你的项目就脱离了 JPA,而直接和Hibernate耦合在一起了。

扩展UserType主要分为两种:
immutable
mutable
今天我先举个immutable的例子。

Java 5提出了一个新的enum类,JPA提供的标准方法是保存enum的name或者是ordinal。这种默认方式能够满足新开发的项目,但是对于一些老项目翻新并不一定适用。下面我们来看一个例子:

public class Status { 


 public static final int ACTIVATED = 5 ; 

 public static final int DEACTIVATED = 6 ; 

}


这个是在java5之前常用的常量定义方法,老项目数据库里面已经保存了很多的5啊6的。现在要把Status改写成enum,而且不希望修改数据库中已有的数据,怎么做?第一反应,status enum可以这么写:

public enum Status { 

 ACTIVATED, 

 DEACTIVATED; 

}


持久化enum的name属性是肯定不用考虑了,ordinal属性呢?这里要保存的可是5和6,而Status enum只有两个实体,他们的ordinal只是0和1。而且项目中还会有其他很多类似的常量类需要改写成enum,JPA的默认方式无法完成任务,这时 候可以开始考虑使用UserType了。

先定义一个接口,这样可以使用一个UserType支持所有类似的enum:

public interface DescriptionID { 


 String getDescription(); 


 int getId(); 

}



然后改写Status enum:

public enum Status implements DescriptionID { 


 ACTIVATED( 5 , " This object is activated " ), 

 DEACTIVATED( 9 , " This object is deactivated " ); 


 private Integer id; 

 private String description; 

 private static List < Status > list; 


 static { 

 list = new ArrayList < Status > ( 2 ); 

 list.add(ACTIVATED); 

 list.add(DEACTIVATED); 

 } 


 private Status( int statusNr, String description) { 

 this .id = statusNr; 

 this .description = description; 

 } 


 public String getDescription() { 


 return this .description; 

 } 


 public Integer getId() { 

 return id; 

 } 


 public static List < Status > getAll() { 

 return list; 

 } 


 public static Status findById(Integer id) { 

 for (Status status : getAll()) { 

 if (id == status.getId()) { 

 return status; 

 } 

 } 

 return null ; 

 } 


}



注意这里每个enum都必须有两个static方法,这些方法名必须在所有的enum中保持一致。List()方法是为了方便获取所有的Status常 量,例如在用户界面通过ComboBox展示,findById()方法是为了通过给定Id获得对应的Enum实例。其中findById()方法参数一 定要是Integer,原因后面会讲到。

下面编写DescriptionIDUserType:


public class DescriptionIDUserType implements UserType, ParameterizedType { 


 private Class enumClass; 


 public void setParameterValues(Properties parameters) { 

 try { 

 enumClass = ReflectHelper.classForName(parameters.getProperty( " class " )); 

 } catch (ClassNotFoundException e) { 

 // TODO Auto-generated catch block 

 e.printStackTrace(); 

 } 


 } 


 public Object assemble(Serializable cached, Object arg1) 

 throws HibernateException { 


 return cached; 

 } 


 /* 

 * (non-Javadoc) 

 * 

 * @see org.hibernate.usertype.UserType#deepCopy(java.lang.Object) 

 */ 

 public Object deepCopy(Object value) throws HibernateException { 

 // TODO Auto-generated method stub 

 return value; 

 } 


 /* 

 * (non-Javadoc) 

 * 

 * @see org.hibernate.usertype.UserType#disassemble(java.lang.Object) 

 */ 

 public Serializable disassemble(Object value) throws HibernateException { 

 // TODO Auto-generated method stub 

 return (Serializable) value; 

 } 


 /* 

 * (non-Javadoc) 

 * 

 * @see org.hibernate.usertype.UserType#equals(java.lang.Object, 

 * java.lang.Object) 

 */ 

 public boolean equals(Object id1, Object id2) throws HibernateException { 

 if (id1 == id2) { 

 return true ; 

 } 

 if (id1 == null || id2 == null ) { 

 return false ; 

 } 


 final DescriptionID did1 = (DescriptionID) id1; 

 final DescriptionID did2 = (DescriptionID) id2; 


 return did1.getId() == did2.getId() 

 && StringUtils.equals(did1.getDescription(), did2 

 .getDescription()); 

 } 


 /* 

 * (non-Javadoc) 

 * 

 * @see org.hibernate.usertype.UserType#hashCode(java.lang.Object) 

 */ 

 public int hashCode(Object value) throws HibernateException { 

 // TODO Auto-generated method stub 

 return value.hashCode(); 

 } 


 /* 

 * (non-Javadoc) 

 * 

 * @see org.hibernate.usertype.UserType#isMutable() 

 */ 

 public boolean isMutable() { 

 // TODO Auto-generated method stub 

 return false ; 

 } 


 /* 

 * (non-Javadoc) 

 * 

 * @see org.hibernate.usertype.UserType#nullSafeGet(java.sql.ResultSet, 

 * java.lang.String[], java.lang.Object) 

 */ 

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

 throws HibernateException, SQLException { 

 try { 

 int id = resultSet.getInt(names[ 0 ]); 

 if (resultSet.wasNull()) { 

 return null ; 

 } 

 return enumClass.getMethod( " findById " , new Class[] { Integer. class }) 

 .invoke( null , id); 

 } catch (IllegalArgumentException e) { 

 // TODO Auto-generated catch block 

 e.printStackTrace(); 

 } catch (SecurityException e) { 

 // TODO Auto-generated catch block 

 e.printStackTrace(); 

 } catch (IllegalAccessException e) { 

 // TODO Auto-generated catch block 

 e.printStackTrace(); 

 } catch (InvocationTargetException e) { 

 // TODO Auto-generated catch block 

 e.printStackTrace(); 

 } catch (NoSuchMethodException e) { 

 // TODO Auto-generated catch block 

 e.printStackTrace(); 

 } 

 return null ; 

 } 


 /* 

 * (non-Javadoc) 

 * 

 * @see org.hibernate.usertype.UserType#nullSafeSet(java.sql.PreparedStatement, 

 * java.lang.Object, int) 

 */ 

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

 throws HibernateException, SQLException { 

 if (value == null ) { 

 statement.setNull(index, Hibernate.INTEGER.sqlType()); 

 } else { 

 DescriptionID dID = (DescriptionID) value; 

 statement.setInt(index, dID.getId()); 

 } 

 } 


 /* 

 * (non-Javadoc) 

 * 

 * @see org.hibernate.usertype.UserType#replace(java.lang.Object, 

 * java.lang.Object, java.lang.Object) 

 */ 

 public Object replace(Object original, Object arg1, Object arg2) 

 throws HibernateException { 

 // TODO Auto-generated method stub 

 return original; 

 } 


 /* 

 * (non-Javadoc) 

 * 

 * @see org.hibernate.usertype.UserType#returnedClass() 

 */ 

 public Class returnedClass() { 

 return DescriptionID. class ; 

 } 


 /* 

 * (non-Javadoc) 

 * 

 * @see org.hibernate.usertype.UserType#sqlTypes() 

 */ 

 public int [] sqlTypes() { 

 return new int []{Hibernate.INTEGER.sqlType()}; 

 } 



}



我们的这个UserType是要支持实现DescriptionID的各种不同的enum,而enum是没法继承的。所以我们需要用户给出具体的参数,以进一步确定到底是哪个enum类。这也就导致了,我们的这个类需要实现 ParameterizedType接口。

由于enum类本身是immutable的,所以这个UserType的实现类相对比较简单,主要的两个方法是 nullSafeGet和 nullSafeSet。 在nullSaftGet中我们使用Java Reflection并借助用户给出的enum类参数直接调用该enum类的findById()方法,这样我们就可以使用数据库中的integer找到 对应的enum实例。注意,由于使用了Java Reflection,所以findById()方法参数必须是Integer而非int。 在nullSafeSet中,我们则通过 DescriptionID接口直接获取enum实例的id属性,并且将它保存到数据库中去。

最后看看怎么使用这个UserType:

@TypeDefs({@TypeDef(name = " status " , typeClass = DescriptionIDUserType. class , 

 parameters = {@Parameter(name = " class " , value = " com.yourpackage.Status " )})}) 

@Entity 

public class SomeObject { 


 private Integer objectId; 

 private Status status; 


 @Id 

 @GeneratedValue(strategy=GenerationType.AUTO) 

 public Integer getObjectId() { 

 return objectId; 

 } 


 public void setObjectId(Integer objectId) { 

 this .objectId = objectId; 

 } 


 @Type(type = " status " ) 

 public Status getStatus() { 

 return status; 

 } 


 public void setStatus(Status status) { 

 this .status = status; 

 } 

}



其中值得讲讲的就是定义Type时使用的parameter,"class"参数是我们自己定义的,该参数为DescriptionIDUserType提供 了具体的enum类。前面已经讲过了,DescriptionIDUserType就是在运行时态利用这个参数自定义enum与数据库之间的持久化逻辑。

使用这个UserType之后,我们就可以在确保数据库数据不变的情况下,成功地将类型不保险的常量类改写成enum,而且这个UserType支持所有实现了 DescriptionID接口的enum类。

类似的情况朋友们可以自由发挥了。


另外一种简单配置方式,不过没验证过.
其实官方这个例子的不仅仅限于EnumUserType的实现,可以适用于任何type和value相互转换的情况,下面是对官方例子做的小改动:

@Parse(toMethod = "literal", fromMethod = "fromLiteral") 

public enum Gender{ 

 ... 

 public String literal(); 


 public static Gender fromLiteral(final String literal); 

}