原創作品,可以轉載,但是請標注出處地址:http://www.cnblogs.com/V1haoge/p/6715063.html
1、回顧
之前的兩篇分別解析了類型別名注冊器和類型處理器注冊器,此二者皆是解析XML映射文件中參數類型與返回結果類型的基礎,別名注冊器用於通過別名找到對應的類類型,類型處理器注冊器則用於通過類類型來找到對應的類型處理器與數據庫類型,以此來完成進出數據庫數據與java之間類型的轉換。
我們在類型處理器注冊器一篇中已經簡單介紹了類型處理器,那就是用於java類型與數據庫類型之間進行映射處理的工具類,這一篇中要詳細解析一下MyBatis中的類型處理器。
2、類型處理器
2.1 類架構
從上面的圖中可以看出MyBatis中整個類型處理器實現架構,TypeHandler接口定義了類型處理器,而TypeReference抽象類則定義了一個類型引用,用於引用一個泛型類型(此處很抽象,不好理解,詳見后續解析),BaseTypeHandler則是類型處理器的基礎,是所有類型處理器的公共模塊,幾乎所有的類型處理器都是通過直接繼承BaseTypeHandler來實現的,這是很明顯使用的是模板模式。
2.2 類型處理器接口:TypeHandler
TypeHandler是用於定義類型處理器的接口,內部很簡單:
1 package org.apache.ibatis.type; 2 import java.sql.CallableStatement; 3 import java.sql.PreparedStatement; 4 import java.sql.ResultSet; 5 import java.sql.SQLException; 6 /** 7 * 類型處理器 8 * 9 */ 10 public interface TypeHandler<T> { 11 12 //設置參數 13 void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; 14 15 //取得結果,供普通select用 16 T getResult(ResultSet rs, String columnName) throws SQLException; 17 18 //取得結果,供普通select用 19 T getResult(ResultSet rs, int columnIndex) throws SQLException; 20 21 //取得結果,供SP用 22 T getResult(CallableStatement cs, int columnIndex) throws SQLException; 23 24 }
通過上述源碼可以看到這個接口中定義了類型處理器基本的四個方法,其中分為兩大類,第一類是設置參數的方法setParameter(),這個方法是用於設置數據庫操作的參數,例如查詢參數、刪除參數、更新參數等;另一類是用於取得結果的方法,這一類方法又細分為兩大種,第一種是從結果集中獲取結果,按照獲取的方式分為兩種:一種是通過列名(columnName)來獲取,另一種是通過列下標(columnIndex)來獲取,這兩種獲取方式正對應我們直接使用JDBC進行數據庫查詢結果中獲取數據的兩種方式,第二種是針對存儲過程而設,通過列下標的方式來獲取存儲過程輸出結果中的數據。
總的來說類型處理器就是兩方面的作用,一方面將Java類型的參數(T prarameter)設置到數據庫操作腳本中(匹配數據庫類型jdbcType),另一種是獲取操作結果到Java類型(T)中。
2.3 類型引用:TypeReference
這個類型引用的作用是用於獲取原生類型,Java中的原生類型又稱為基本類型,即byte、short、int、long、float、double、boolean、char八大基本數據類型。
這個類有必要重點講解一下,同時也是為了加強一下Java中類型的概念,來看源碼:
1 package org.apache.ibatis.type; 2 3 import java.lang.reflect.ParameterizedType; 4 import java.lang.reflect.Type; 5 6 /** 7 * References a generic type. 8 * 9 * @param <T> the referenced type 10 * @author Simone Tripodi 11 * @since 3.1.0 12 * 3.1新加的類型引用,為了引用一個泛型類型 13 */ 14 public abstract class TypeReference<T> { 15 16 //引用的原生類型 17 private final Type rawType; 18 19 protected TypeReference() { 20 rawType = getSuperclassTypeParameter(getClass()); 21 } 22 23 Type getSuperclassTypeParameter(Class<?> clazz) { 24 //得到泛型T的實際類型 25 Type genericSuperclass = clazz.getGenericSuperclass(); 26 if (genericSuperclass instanceof Class) { 27 // try to climb up the hierarchy until meet something useful 28 if (TypeReference.class != genericSuperclass) { 29 return getSuperclassTypeParameter(clazz.getSuperclass()); 30 } 31 throw new TypeException("'" + getClass() + "' extends TypeReference but misses the type parameter. " 32 + "Remove the extension or add a type parameter to it."); 33 } 34 //獲取泛型<T>中的T類型 35 Type rawType = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0]; 36 // TODO remove this when Reflector is fixed to return Types 37 if (rawType instanceof ParameterizedType) { 38 rawType = ((ParameterizedType) rawType).getRawType(); 39 } 40 return rawType; 41 } 42 43 public final Type getRawType() { 44 return rawType; 45 } 46 47 @Override 48 public String toString() { 49 return rawType.toString(); 50 } 51 52 }
這個抽象類也是被BaseTypeHandler所繼承的,也就意味着幾乎所有的內置類型處理器都繼承了這個類,那么這個類型引用的目的到底是什么呢?
這個問題稍后再說,我們先解析下源碼:
這個類在其無參構造器中通過調用getSuperclassTypeParameter()方法為其內部定義的final型字段rawType賦值,其參數是getClass()方法的結果,這是Object類中定義的方法,這個方法返回的是當前類(實例)的類類型。
重點在於getSuperclassTypeParameter()方法中:
第一步:通過給定參數clazz的getGenericSuperclass()方法來獲取該類類型的上一級類型(直接超類,父類,即參數類類型繼承的類的類型)並帶有參數類型,即帶泛型。如果要獲取不帶泛型的父類可使用getSuperclass()方法。
第二步:判斷第一步獲取的類型是否是Class類的實例
Class類的實例有哪些呢?
其實每一個類都是Class類的實例,Class類是對Java中類的抽象,它本身也是一個類,但它是處於普通類上一層次的類,是類的頂層抽象。從JDK文檔中可獲知“Instances of the class represent classes and interfaces in a running Java application.”(意為:Class的實例表示的是在一個運行的應用中的所有類和接口)
,那么我們就明白了,Class類的實例就是接口與類。那么Java中有哪些不是Class類的實例呢?泛型類,不錯,如果一個類是泛型類,那么他就不再是Class類的實例,為什么呢?
泛型類是Java中一種獨特的存在,它一般用於傳遞類(更准確的說是傳遞類型),類似於一般方法中傳遞對象的概念,它不是簡單的類,而是一種帶有抽象概念性質的一種類,它會通過所傳遞的類(參數化類)來指定當前類所代表的是基於基本類型中的哪一類類型。(通過兩種類型來確定具體的類型(最后這個類型表示的是泛型類型整體表達的類型))
第二步:如果第一步獲取的類型是帶泛型的類型,那么判斷不成立,則會直接執行第35行代碼,將該類型強轉為參數化類型,使用其getActualTypeArguments()方法來獲取其參數類型(泛型類型),因為該方法獲取的泛型類型可能不是一個,所以返回的是一個數組,但是我們這里只會獲取到一個,所以取第一個即可。
但是如果第一步獲取的類型不帶泛型,那么就會進入條件內部執行,再次判斷,獲取的類型是否是TypeReference類型,如果不是該類型,則有可能是多重繼承導致目標類型並不是直接繼承自TypeReference,那么我們通過getSuperclass()方法獲取其父類,以這個類來進行遞歸;但如果獲取到的是TypeReference類型,只是沒有添加泛型,則拋出類型異常,提示丟失泛型。
第三步:如果第二步判斷不通過,則會執行地35行代碼,來獲取參數類型,然后對獲取的參數類型進行判斷,如果該類型還是參數化類型(仍然帶有泛型,即泛型嵌套的模式),那么就需要再次執行getActualTypeArguments()方法來獲取其泛型類型(參數類型),最后將該類型返回(賦值給字段)
為什么只會獲取兩次呢?因為,通過之前的類架構我們已經明白,具體的類型處理器最多只會存在兩層繼承。
最后說一下,這個類型引用的目的,它就是為了持有這個具體的類型處理器所處理的Java類型的原生類型。我們可以看到在該類中還有兩個方法getRawType()和toString()方法,這兩個方法都是public修飾的,是對外公開的方法,那么也就意味着這個原生類型是為了被外部調用而設。
通過檢索發現,getRawType()方法重點被調用的地方在TypeHandlerRegistry(類型處理器注冊器)中,在沒有指定JavaType而只有TypeHandler的情況下,調用該TypeHandler的getRawType()方法來獲取其原生類型(即參數類型)來作為其JavaType來進行類型處理器的注冊。
2.4 基礎類型處理器:BaseTypeHandler
BaseTypeHandler繼承了TypeReference抽象類,實現了TypeHandler接口,它本身仍然是抽象類,在它內部簡單的實現了TypeHandler接口中定義的四個方法中的部分功能,所謂部分功能是指只實現了所有類型處理器公共部分,具體的不同處理部分則還是交由具體的類型處理器來自己實現,所有它內部再次定義了四個抽象類,用來指導具體類型處理器的實現。
BaseTypeHandler中主要對設置參數與獲取返回結果時數據位null的情況進行了處理,具體的參數設置方法與結果獲取方法都是由具體的類型處理器來實現的。
1 //非NULL情況,怎么設參數還得交給不同的子類完成 2 public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; 3 4 //以下3個方法是取得可能為null的結果,具體交給子類完成 5 public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException; 6 7 public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException; 8 9 public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;
上面的四個方法就是BaseTypeHandler中定義的抽象方法。MyBatis內置的類型處理器幾乎都是通過繼承實現上面的四個方法來完成最終定義的。
2.5 類型處理器:StringTypeHandler
我們看個簡單的例子來理解一下這個過程。下面是字符串類型處理器:StringTypeHandler的源碼
1 package org.apache.ibatis.type; 2 3 import java.sql.CallableStatement; 4 import java.sql.PreparedStatement; 5 import java.sql.ResultSet; 6 import java.sql.SQLException; 7 /** 8 * String類型處理器 9 * 調用PreparedStatement.setString, ResultSet.getString, CallableStatement.getString 10 */ 11 public class StringTypeHandler extends BaseTypeHandler<String> { 12 13 @Override 14 public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) 15 throws SQLException { 16 ps.setString(i, parameter); 17 } 18 19 @Override 20 public String getNullableResult(ResultSet rs, String columnName) 21 throws SQLException { 22 return rs.getString(columnName); 23 } 24 25 @Override 26 public String getNullableResult(ResultSet rs, int columnIndex) 27 throws SQLException { 28 return rs.getString(columnIndex); 29 } 30 31 @Override 32 public String getNullableResult(CallableStatement cs, int columnIndex) 33 throws SQLException { 34 return cs.getString(columnIndex); 35 } 36 }
上面的源碼完美的詮釋了之前的解析,具體的類型處理器中只需要實現這四個方法即可,前提是其繼承了BaseTypeHandler抽象類。
其中設置參數的方法中具體的實現調用了PreparedStatement的setString()方法,這個是我們很熟悉的方法。同樣的,在獲取結果的方法中也是通過調用ResultSet的getString()方法,和CallableStatement的getString()方法來完成具體的功能。這已經是MyBatis中最為底層的邏輯了,因為它直接調用了JDK API來實現功能。
2.6 未知類型處理器:UnknownTypeHandler
這個是MyBatis中定義的一個較為特殊的類型處理器,雖然其內部實現和普通的類型處理器如出一轍,但是它擁有一些特殊的地方,所以單獨拿出來說一說。
通過類型處理器注冊器中的注冊信息可以看出這種類型處理器所對應的JavaType是Object類型,對應的JdbcType是OTHER類型,這個OTHER是什么類型?我們可以這么理解,市面上數據庫種類繁多,而且各有特點,這些數據庫產品即滿足SQL規范,同時也有各自的擴展和強化,每個數據庫內部都有一些自定義的只在其內部起作用的數據類型,而這些類型反映到Java中之后是Object類型時,這里就將其統一定義為OTHER類型。
1 private static final ObjectTypeHandler OBJECT_TYPE_HANDLER = new ObjectTypeHandler(); 2 3 private TypeHandlerRegistry typeHandlerRegistry; 4 5 public UnknownTypeHandler(TypeHandlerRegistry typeHandlerRegistry) { 6 this.typeHandlerRegistry = typeHandlerRegistry; 7 } 8 9 @Override 10 public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) 11 throws SQLException { 12 TypeHandler handler = resolveTypeHandler(parameter, jdbcType); 13 handler.setParameter(ps, i, parameter, jdbcType); 14 } 15 16 @Override 17 public Object getNullableResult(ResultSet rs, String columnName) 18 throws SQLException { 19 TypeHandler<?> handler = resolveTypeHandler(rs, columnName); 20 return handler.getResult(rs, columnName); 21 } 22 23 @Override 24 public Object getNullableResult(ResultSet rs, int columnIndex) 25 throws SQLException { 26 TypeHandler<?> handler = resolveTypeHandler(rs.getMetaData(), columnIndex); 27 if (handler == null || handler instanceof UnknownTypeHandler) { 28 handler = OBJECT_TYPE_HANDLER; 29 } 30 return handler.getResult(rs, columnIndex); 31 } 32 33 @Override 34 public Object getNullableResult(CallableStatement cs, int columnIndex) 35 throws SQLException { 36 return cs.getObject(columnIndex); 37 }
源碼分析:在UnknownTypeHandler中的四個方法中,除針對存儲過程結果取數據的情況之外,其余三個方法的實現均類似,都是先通過不同的resolveTypeHandler()方法來獲取具體的TypeHandler,然后調用具體TypeHandler的對應方法來完成功能。那么UnknownTypeHandler中的重點就集中在這三個resolveTypeHandler()方法中了。
1 private TypeHandler<? extends Object> resolveTypeHandler(Object parameter, JdbcType jdbcType) { 2 TypeHandler<? extends Object> handler; 3 if (parameter == null) { 4 handler = OBJECT_TYPE_HANDLER; 5 } else { 6 handler = typeHandlerRegistry.getTypeHandler(parameter.getClass(), jdbcType); 7 // check if handler is null (issue #270) 8 if (handler == null || handler instanceof UnknownTypeHandler) { 9 handler = OBJECT_TYPE_HANDLER; 10 } 11 } 12 return handler; 13 } 14 15 private TypeHandler<?> resolveTypeHandler(ResultSet rs, String column) { 16 try { 17 Map<String,Integer> columnIndexLookup; 18 columnIndexLookup = new HashMap<String,Integer>(); 19 ResultSetMetaData rsmd = rs.getMetaData(); 20 int count = rsmd.getColumnCount(); 21 for (int i=1; i <= count; i++) { 22 String name = rsmd.getColumnName(i); 23 columnIndexLookup.put(name,i); 24 } 25 Integer columnIndex = columnIndexLookup.get(column); 26 TypeHandler<?> handler = null; 27 if (columnIndex != null) { 28 handler = resolveTypeHandler(rsmd, columnIndex); 29 } 30 if (handler == null || handler instanceof UnknownTypeHandler) { 31 handler = OBJECT_TYPE_HANDLER; 32 } 33 return handler; 34 } catch (SQLException e) { 35 throw new TypeException("Error determining JDBC type for column " + column + ". Cause: " + e, e); 36 } 37 } 38 39 private TypeHandler<?> resolveTypeHandler(ResultSetMetaData rsmd, Integer columnIndex) throws SQLException { 40 TypeHandler<?> handler = null; 41 JdbcType jdbcType = safeGetJdbcTypeForColumn(rsmd, columnIndex); 42 Class<?> javaType = safeGetClassForColumn(rsmd, columnIndex); 43 if (javaType != null && jdbcType != null) { 44 handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType); 45 } else if (javaType != null) { 46 handler = typeHandlerRegistry.getTypeHandler(javaType); 47 } else if (jdbcType != null) { 48 handler = typeHandlerRegistry.getTypeHandler(jdbcType); 49 } 50 return handler; 51 } 52 53 private JdbcType safeGetJdbcTypeForColumn(ResultSetMetaData rsmd, Integer columnIndex) { 54 try { 55 return JdbcType.forCode(rsmd.getColumnType(columnIndex)); 56 } catch (Exception e) { 57 return null; 58 } 59 } 60 61 private Class<?> safeGetClassForColumn(ResultSetMetaData rsmd, Integer columnIndex) { 62 try { 63 return Resources.classForName(rsmd.getColumnClassName(columnIndex)); 64 } catch (Exception e) { 65 return null; 66 } 67 }
第一個resolveTypeHandler方法是由設置參數的方法調用的,目的在於獲取真正的TypeHandler來進行類型處理。如果其參數parameter為null,那么直接將TypeHandler設定為ObjectTypeHandler,如果parameter不為null,則直接從類型處理器注冊器中獲取對應JavaType與JdbcType的類型處理器,這里存在一個#270BUG,針對無法再類型處理器注冊器中獲取TypeHandler獲取獲取到的是UnknownTypeHandler的情況進行再次處理:賦值ObjectTypeHandler。
第二個resolveTypeHandler方法是被通過列名來獲取結果數據的方法所調用的,目的同上。首先通過結果集原數據將結果集中的數據循環存放到一個HashMap集合中(以列名為鍵,列下標為值),然后從中獲取給定列名的下標值,如果集合中存在該列名(即能獲取到列下標),則調用第三個resolveTypeHandler()方法通過列下標方式來獲取具體TypeHandler。當然如果不存在這個列名(亦即獲取不到列下標),則直接賦值ObjectTypeHandler。
第三個resolveTypeHandler方法是被通過列下標來獲取結果數據的方法所調用的,同時也被第二個resolveTypeHandler方法所調用。分別通過safeGetJdbcTypeForColumn()方法和safeGetClassForColumn()方法來獲取列下標所對應數據的JdbcType與JavaType,然后針對獲取到的JdbcType和JavaType來從類型處理器注冊器中獲取具體的類型處理器。這里分三種情況來獲取:jdbcType與JavaType均不為null的情況、只有JavaType不為null的情況和只有JdbcType不為null的情況,三者情況分別調用三種getTypeHandler()方法來完成獲取功能。
總結:由此可見UnknownTypeHandler是一種中間類型處理器,或者叫代理類型處理器,因為它本身並不會真正實現處理功能,它只是通過獲取對應的類型處理器來調用其處理功能來完成功能。
3、自定義類型處理器
有關自定義類型處理器,我們只做簡單介紹,其實它也很是簡單,我們只要繼承BaseTypeHandler<T>抽象類即可,實現其中的四個方法。我們這里舉個簡單的例子,假如說MyBatis內置的StringTypeHandler無法滿足我們的需求,我們可以對其進行擴展自定義,我們自定義一個新的字符串類型處理器:MyStringTypeHandler,代碼如下:
1 package org.apache.ibatis.type; 2 3 import java.sql.CallableStatement; 4 import java.sql.PreparedStatement; 5 import java.sql.ResultSet; 6 import java.sql.SQLException; 7 8 public class MyStringTypeHandler extends BaseTypeHandler<String> { 9 10 @Override 11 public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) 12 throws SQLException { 13 System.out.println("新的邏輯"); 14 ps.setString(i, parameter); 15 System.out.println("新的邏輯"); 16 } 17 18 @Override 19 public String getNullableResult(ResultSet rs, String columnName) throws SQLException { 20 System.out.println("新的邏輯"); 21 return rs.getString(columnName); 22 } 23 24 @Override 25 public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { 26 System.out.println("新的邏輯"); 27 return rs.getString(columnIndex); 28 } 29 30 @Override 31 public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { 32 System.out.println("新的邏輯"); 33 return cs.getString(columnIndex); 34 } 35 36 }
定義好類型處理器之后,然后我們需要的就是將自定義的類型處理器注冊到TypeHandlerRegistry中,方法也簡單。
1 <typeHandlers> 2 <typeHandler handler="org.apache.ibatis.type.MyStringTypeHandler"/> 3 </typeHandlers>
當然我們也可以指定JavaType與jdbcType,獲取直接使用package方式進行設置,但是如果你只是自定義了很少的類型處理器,沒有必要采用package方式設置,因為這種方式會掃描整個包下的類,無形中造成了時延。
然后這個新的類型處理器就會添加到TypeHandlerRegistry中了,它會在背后默默實現功能。
4、總結
至此我們將Type模塊解析完畢,說的很是粗糙,但這是自己學習提高的過程,特此記錄,期待下一篇。