PC的blog

Finding... Thinking... Solving...

BlogJava 首页 新随笔 联系 聚合 管理
  9 Posts :: 0 Stories :: 54 Comments :: 0 Trackbacks
前段时间写了本书《Hibernate 3和Java Persistence API 程序开发从入门到精通》,书中着重介绍了在Hibernate/JPA中使用Annotation。最近有读者来信询问UserType,再加上最近看到有的人在项目中滥用Hibernate的user type,想在这里说几句。

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

扩展UserType主要分为两种:
  1. immutable
  2. 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类。

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

关于Annotation和Usertype的相关知识请参考我写的《Hibernate 3和Java Persistence API 程序开发从入门到精通》





声明:本文版权归作者所有,如需转载请注明出处。

posted on 2008-03-21 20:14 polygoncell 阅读(2935) 评论(4)  编辑  收藏

Feedback

# re: Hibernate user type 2008-03-21 20:22 333333333
人家用你管得着吗?  回复  更多评论
  

# re: Hibernate user type 2008-03-21 21:57 ci
不错...  回复  更多评论
  

# re: Hibernate user type 2008-03-21 22:41 葛京
谢谢捧场,能用得上就好。  回复  更多评论
  

# re: Hibernate user type 2008-03-29 00:19 polygoncell

本书的命题是“入门和精通”,网上提供的章节仅仅是入门级别的内容,是为那些完全没有Hibernate基础的同学准备的。 那些已经了解Hibernate的同学一定会觉得这些章节很乏味,这是很正常的,因为你们已经掌握了这些入门级别的内容,再看一遍,自然乏味。但是请你们为那些从来没有接触过Hibernate的同学考虑一下,他们非常需要一个相对浅显易懂的台阶来帮助他们“入门”。这就是我撰写前几章入门内容的初衷。

对于那些已经了解Hibernate的朋友们,请你们静下心来阅读后面深入内核的章节,在这些章节中,我是从构架的角度讲解了Hibernate的几个主要的模块,举例印证,图文并茂,大部分内容源于实际项目。如果通读完全书,还有朋友认为这本书“不怎么样”,那么我作为这本书的作者,在这里诚心诚意的期盼着你们的宝贵意见,对于正确的意见,我将会在本书的后续版本中加以采纳。

不论如何,非常感谢大家对本书的关注。  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航:
博客园   IT新闻   Chat2DB   C++博客   博问