自定义数据类型映射

自定义数据类型映射
Hibernate 提供了自定义映射类型接口,允许用户以编程的方式创建自定义映射类型,以便把持久化类的任意类型的属性映射到数据库中。
 
现在我们以一个简单的例子来说明自定义数据类型映射的思想和用法,对于 T_User 对象需要增加一个 email 属性用于保存用户的邮件地址,同时要求一个用户可以有多个邮件地址,系统在发送邮件时将向这些地址同时发送。
  根据我们以往的项目经验主要有两种思路:
   1 )、为 T_User 表增加 email1,email2…… 等字段。
   2 )、增加一个 T_Email 表, T_User 表通过主键与之关联。
1 种方式实现不够优雅,并且在 email 数量上和可查询性上受到较大的局限,但是由于是单表操作开发难度较小,性能相对较高。
2 种方式比较完美的体现了设计意图,也无 email 数量限制且易于查询,但是仅仅为了一个 email 就增加一张表未免有些小题大做。
其实我们可以将一个用户的所有 email 信息都存在 T_User 表中的 email 字段中,每个 email 用分号分开,这样我们就得到了一个大字符串,但是这就需要上层的程序逻辑处理,我们的目的是为数据逻辑层及业务逻辑层提供更加易于操作的对象。 Hibernate 中的 UserType 接口正是为我们实现这一目的准备的,通过实现该接口我们可以定义我们自己的类型,并且可加入相应的数据解析逻辑,而且他还会带来一个好处那就是重用性(所有需要保存 email 的实体都可以共用该类型)。根据这里的情况如果我们将 email 映射为 List 类型将会是一个不错的选择,如何将 email 映射为 List 类型呢?下面我们将会看到。
T_User

T_User 
id     number   <pk>
name varchar2(50)
age    number
image    BLOB
resume   CLOB
email     varchar2(500)          

首先我们先来认识一下 UserType 接口:
 
 
 
public interface UserType{
 /**
返回 UserType 所映射字段的 sql 类型 (java.sql.Types)
返回类型时 int[], 其中包含映射各自段的 sql 代码
UserType 可以映射到一个或多个字段)
 **/
 public int[] getTypes();
 /**
   
UserType.nullSafeGet()
所返回的自定义数据类型
 **/
 public Class returnedClass();
 /**
自定数据类型的对比方法
此方法用于做脏数据检查,参数 x,y 分别代表属性当前值和属性的快照值
如果 equals 方法返回 false, Hibernate 将认为数据发生变化,并将变化更新到数据库中
 **/
 public boolean equals(Object x,Object y) throws HibernateException;
 /**
Hibernate 加载数据时会调用该方法,该方法会从底层 JDBC ResultSet 读取数据,将其
转化为自定义类型后返回,该方法要求对可能出现的 null 值作处理, names 中包含了当前自定义类型的映射字段名称。
 **/
 public Object nullSafeGet(ResultSet rs,String[] names,Objec owner) throws HibernateException,SQLException;
 /**
   本方法将在 Hibernate 进行数据库保存时被调用我们可以通过 PrepareStatement 将自定义数据
   写入数据库
 **/
 public void nullSafeSet(PrepareStatement st,Object value,int index) throws HibernateException,SQLException;
/**
  提供自定义类型的完全复制方法本方法将用于构造返回对象,当 nullSafeGet 方法被调用后,我们
  获得了自定义数据对象。在向用户返回自定义数据之前, deepCopy 方法将被调用,它将根据自定
  义数据对象构造完全拷贝,并将此拷贝返回给用户使用。
  此时,我们就得到了自定义数据对象的两个版本,第一个是从数据库读取的原始版本将由 Hibernate 负责维护,复制版本将由用户使用,原版本用作稍后的脏数据检查依据; Hibernate 将在脏数据检查过程中将这两个版本数据进行比对 ( 通过调用 equals 方法 ) ,如果数据发生变化 (equals 将返回 false) ,则执行对应的持久化操作。
**/
public Object deepCopy(Object value) throws HibernateException;
/**
  判断本实例是否是可变的, Hibernate 在处理不可变类型时会采取一些性能优化措施。
**/
 public boolean isMutable();
}
实现我们的 EmailList ,实现了 UserType 接口代码如下:
public class EmailList implements UserType{
 private List emails;
 private static final char SPLIT=”;”;
 private static final int[] TYPES=new int[]{Types.VARCHAR};
 public boolean isMutable(){
    return false;
}
 public int[] sqlTypes(){
 return TYPES;
}
public Class returnedClass(){
 return List.class;
}
/**
  创建一个新的 List 实例,包含原有 List 实例所有的元素
**/
public Object deepCopy(Object value) throws HibernateException{
 List sourcelist=(List)value;
 List targetlist=new ArrayList();
 targetlist.addAll(sourcelist);
 return targetlist;
}
/**
  判断 email list 是否发生变化
**/
public boolean equals(Object x,Object y) throws HibernateException{
 if(x==y)return ture;
 if(x!=null && y!=null){
     List xlist=(List)x;
     List ylist=(List)y;
     if(xlist.size()!=ylist.size()) return false;
     for(int i=0;i<xlist.size();i++){
      String str1=xlist.get(i).toString();
      String str2=ylist.get(i).toString();
      if(!str1.equals(str2))return false;
     }
     return true;
}
return false;
}
/**
  ResultSet 中取出 email 字段,并将其解析为 List 类型后返回
**/
public Object nullSafeGet(ResuleSet rs String[] names,Object owner)
 throws HibernateException,SQLException{
 String value=(String)Hibernate.STRING.nullSafeGet(rs,name[0]);
 if(value!=null){
   return parse(value);
 }
 else{
   return null;
}
}
/**
  List 型的 email 信息组装成字符串之后保存到 email 字段
**/
public void nullSafeSet(PrepareStatement st,Object value,int index)
 throws HibernateException,SQLException{
 if(value!=null){
   String str=assemble((List)value);
   Hibernate.STRING.nullSafeSet(st,str,index);
 }
 else{
   Hibernate.STRING.nullSafeSet(st,value,index);
 }
}
/**
  String 拼装为一个字符串,以 ”;” 分隔
**/
private String assemble(List list){
 StringBuffer buffer=new StringBuffer();
 for(int i=0;i<list.size()-1;i++){
    buffer.append(list.get(i).toString()).append(SPLIT);
 }
 buffer.append(list.get(list.size()-1).toString());
 return buffer.toString();
}
/**
  将以 ”;” 分隔的字符串解析为一个字符串数组
**/
public List parse(String value){
 List emaillist=new ArrayList();
 String[] strs=org.apache.commons.lang.StringUtils.split(SPLIT);
 for(int i=0;i<strs.length;i++){
    emaillist.add(strs[i]);
 
}
return emaillist;
}
}
 
TUser.hbm.xml
<hibernate-mapping>
 <class name=”com.neusoft.hibernate.entity.TUser” table=”T_USER”>
 <id…../>
 <property……/>
 <property name=”email” column=”email” type=”com.neusoft.hibernate.entity.type.EMallist” />
 </class>
</hibernate-mapping>
 
测试程序:
TUser user=(TUser)session.load(TUser.class,new Integer(2));
List list=user.getEmail();
for(int i=0;i<list.size();i++){
 System.out.println(list.get(i));
}
list.add(“zhao-xin@neusoft.com”);
Transaction trans=session.beginTransaction();
 session.save(user);
trans.commit();
观察数据库发现 email 字段已经以 ”;” 分隔的形势存在,同样在读取时,我们也无需面对原始的 ”;” 分隔字符串,转而只需处理 List 型数据即可。
使用 UserType 处理大数据对象:
还记得我们在上一篇文章有关大数据对象映射技术中留下的伏笔吗?现在我们就来兑现,我们将使用 UserType 构造自定义数据类型,来给出一个大数据对象的通用解决方案(针对 Oracle 数据库)。下面的 StringClobType 实现了这一目标。
public class StringClobType implements UserType{
 private static final String ORACLE_DRIVER_NAME=”Oracle JDBC driver”;
 private static final int ORACLE_DRIVER_MAJOR_VERSION=9;
 private static final int ORACLE_DRIVER_MINOR_VERSION=0;
 
 public int[] sqlTypes(){
   return new int[]{Types.CLOB};
 }
 public Class returnedClass(){
   return String.class;
 }
 public boolean equals(Object x,Object y){
   return org.apache.commons.lang.ObjectUtils.equals(x,y);
 }
 public Object nullSafeGet(Resultset rs,String[] names,Object owner)
throws HibernateException,SQLException{
 Clob clob=rs.getClob(names[0]);
return (clob==null?null:clob.getSubString(1,(int)clob.length()));
}
public void nullSafeSet(PrepareStatement st,Object value,int index)
     throws HibernateException,SQLException{
   DatebaseMetaData dbMetaData=st.getConnection().getMetaData();
   if(value==null){
    st.setNull(index,sqlTypes()[0]);
}
// 本实现只适用于 Oracle 数据库 9.0 以上版本
if(ORACLE_DRIVER_NAME.equals(dbMetaData.getDriverName())){
 if((dbMetaData.getDriverMajorVersion()>=ORACLE_DRIVER_MAJOR_VERSION)
     && (dbMetaData.getDriverMinorVersion()>=ORACLE_DRIVER_MINOR_VERSION)){
       try{
        // 通过动态加载方式避免编译期间对 Oracle JDBC 的依赖
        Class oracleClobClass=Class.forName(“oracle.sql.CLOB”);
        // 动态调用 createTemporary 方法
        Class partypes[]=new Class[3];
        partypes[0]=Connection.class;
        partypes[1]=Boolean.class;
        partypes[2]=Integer.class;
        Method createTemporaryMethod=
oracleClobClass.getDeclaredMethod(“createTemporary”,partypes);
        Field durationSessionField=oracleClobClass.getField(“DURATION_SESSION”);
        Object[] arglist=new Object[3];
        Connection conn=st.getConnection().getMetaData().getConnection();
        // 数据库连接类型必须为 OracleConnection ,某些应用服务器会使用自带的
        //Oracle JDBC Wrapper, Weblogic, 这里需要特别注意
     Class oracleConnectionClass=Class.forName(“oracle.jdbc.OracleConnection”);
        if(!oracleConnectionClass.isAssignableForm(conn.getClass())){
          throw new HibernateException(“Must be a oracle.jdbc.OracleConnection:”
+conn.getClass.getName());
        }
        agrlist[0]=conn;
        arglist[1]=Boolean.class;
        arglist[2]=durationSessionField.get(null);
       
        Object tempClob=createTemporaryMethod.invoke(null,arglist);
        partypes[0]=Integer.TYPE;
        Method openMethod=oracleClobClass.getDeclaredMethod(“open”,partypes);
        Field modeReadWriteField=oracleClobClass.getField(“MODE_READWRITE”);
        arglist=new Object[1];
        arglist[0]= modeReadWriteField.get(null);
        openMethod.invoke(tempClob,arglist);// 按读写模式打开Clob对象的输入流
        Method getCharacterOutputStreamMethod=oracleClobClass.getDeclaredMethod(
                                               “getCharacterOutputStream”,null);
        // 调用 getCharacterOutputStream 方法
        Writer tempClobWriter=
(Writer)getcharacterOutputStreamMethod.invoke(tempClob,null);
        // 将数据写入 Clob
        tempClobWriter.write((String)value);
        tempClobWriter.flush();
        tempClobWriter.close();
        // 关闭 Clob
     Method closeMethod=oracleClobClass.getDeclareMethod(“close”,null);
        closeMethod.invoke(tempClob,null);
        st.setClob(index,(Clob)tempClob);
       }catch(ClassNotFoundException ce){
         throw new HibernateException(“Unable to find a require class./n”+ce.getMessage());
       } catch(NoSuchMethodException me){
         throw new HibernateException(
“Unable to find a require method./n”+me.getMessage());
       } catch(NoSuchFieldException fe){
         throw new HibernateException(“Unable to find a require field./n”+fe.getMessage());
       } catch(IllegalAccessException ae){
         throw new HibernateException(
“Unable to find a require method or field./n”+ae.getMessage());
       }catch(InvocationTargetException ie){
         throw new HibernateException(ie.getMessage());
       }catch(IOException oe){
         throw new HibernateException(oe.getMessage());
       }
}else{
 throw new HibernateException(“No CLOBS support.Use driver version”+
ORACLE_DRIVER_MAJOR+”,minor ”
+ORACLE_DRIVER_MINOR_VERSION);
}
}else{
 String str=(String)value;
 StringReader r=new StringReader(str);
 st.setCharacterStream(index,r,str.length());
}
}
 
public Object deepCopy(Object value){
 if(value==null)
    return null;
 return new String((String)value);
}
public boolean isMutable(){
 return false;
}
}
上面这段代码重点在 nullSafeSet 方法的实现,在该方法中通过 java reflection 机制摆脱了编译期的
Oracle JDBC 原生类依赖,同时借助 Oracle JDBC 提供的原生功能完成 Clob 字段的写入,该代码是从 Ali Ibrahim,Scott Miller 的代码修改而来,支持 Oracle 9 以上版本, Oracle 8 对应的实现参照
http://www.hibernate.org/56.html 网址提供的解决方案。另外此代码必须运行在最新版的
Oracle JDBC Driver 上。
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值