自定義數據類型映射

自定義數據類型映射
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(“[email protected]”);
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上。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章