(點選上方公眾號,可快速關註)
來源:岑凱倫(微信公號 – 凱倫說,ID:KailunTalk) ,
my.oschina.net/kailuncen/blog/913880
TypeHandlers
無論是 MyBatis 在預處理陳述句(PreparedStatement)中設定一個引數時,還是從結果集中取出一個值時, 都會用型別處理器將獲取的值以合適的方式轉換成 Java 型別。
下麵是常見的一些對應型別:
以BigDecimalTypeHandler看一下,它主要完成了哪些工作。
這個類的第一個方法是對預處理陳述句(PreparedStatement)設定引數,之後的三個函式都是從ResultSet或者用於執行儲存過程的CallableStatement陳述句中獲取BigDecimal型別的數值,用於向BigDecimal型別的Java欄位賦值。 BigDecimalTypeHandler繼承的BaseTypeHandler是個泛型類,其他的TypeHandler也是透過繼承這個抽象類,實現其中的抽象方法,實現型別轉換的工作。
這個抽象類實現了TypeHandler介面,這個介面主要定義了型別轉換的幾種操作。
至於這個抽象類繼承的TypeReference
如何使用
大致介紹了TypeHandler的作用,及其相關類,我們來看看如何使用它。 今天遇到的主要是從SqlServer中取資料,遇到很多列都是Numeric(10,2)型別,指的是欄位是數字型,長度為10,小數為兩位。Mybatis預設的BigDecimalTypeHandler取到後,都預設變成4位小數,不夠的補了0。而上層的要求是,拿到的和數字相關的資料都要2位小數。
有兩種做法,一種是在所有給上層賦值的時候,都人工對BigDeciam的資料做如下操作。
setScale(2, BigDecimal.ROUND_HALF_UP)
因為這是一個全域性性的要求,所有相關的地方,都需要有這個程式碼,雖然可以寫一個工具類,各個地方呼叫,但就對原本間接的程式碼造成了侵入。既然這樣,為什麼不試試TypeHandler。
我的做法是繼承BigDecimalTypeHandler,改寫原來的取值方法,對取到的數值做範圍限定。
加上@MappedJdbcTypes註解是為了表明這個類是用於對映JdbcType的NUMERIC型別,這會改寫預設的用於轉換Java BegDecimal和Jdbc NUMERIC的BigDecimal,在後面原始碼中可略窺一二。
開發完這個轉換類後,你需要在Mybatis的配置檔案中宣告這個TypeHandler,這樣Mybatis才知道你自己宣告了一個TypeHandler。
這樣TypeHandler就起作用了。下麵是前後效果。
原始碼層面
首先Mybatis有一個預設的TypeHandler實現,這些TypeHandler是如何被Mybatis識別的呢。 答案是TypeHandlerRegistry。在Mybatis初始化配置的時候,TypeHandlerRegistry會把JdbcType和Java型別對應的對映關係註冊進該類內部的Map中。
JDBC_TYPE_HANDLER_MAP中記錄的是JdbcType和TypeHandler對應的關係。
TYPE_HANDLER_MAP中記錄的是Java型別和對應的所有JdbcType以及其對應TypeHandler的對映關係關係。
UNKNOWN_TYPE_HANDLER是在執行BaseTypeHandler的抽象方法時,去先解析出來該用什麼TypeHandler,目前還沒用到,先不研究。 ALL_TYPE_HANDLERS_MAP中記錄的是所有TypeHandler的Class和其實體之間的對映關係。
我們以系統預設註冊的三個作為例子,看看整個執行的流程
register(String.class, new StringTypeHandler());
register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
register(JdbcType.NCHAR, new NStringTypeHandler());
第一個是告訴 String型別的轉換,要用StringTypeHandler。 直接進的這個函式,因為我們的TypeHandler上並沒有打註解,因此直接進入箭頭標記的邏輯。
然後繼續註冊,只不過jdbcType是null。
後續的程式碼比較簡單,會先從Type_HANLER_MAP中看是否有已經存在的 Map
第二個,傳入了jdbcType是NCHAR,和第一個類似,但直接就進入了最後一步的註冊環節,沒有去判斷傳入什麼樣的jdbcType型別,因為已經指定了。
第三個是系結了 jdbcType和Handler之間的對應關係。
OK,前面是系統預設註冊進去的,那我們看一下我們在如何使用章節中新增進去的SubBigDecimalTypeHandler是如何被註冊進去的呢。
Mybatis在應用中啟動時,會根據XML檔案初始化配置,負責解析XML生成配置類的就是XMLConfigBuilder,透過呼叫其中的parseConfiguration方法填充配置類。
箭頭表示處,就是解析typehandlers節點,我們看看他具體做了些什麼。
因為我們不是對整個package進行註冊,所以進入else分支,因為只表明瞭一個最簡單的Handler,所以要獲取的欄位都為null,由此我們也可以看出,在編寫XML時,我們也是可以直接指定對映關係的,因為獲取不到javaType和jdbcType,後面應該是會根據這個類再解析一波。跟註冊相關的又回到了TypeHandlerRegistry這個類裡面,職責還是很清晰的。
在這個方法裡面,首先會獲取有沒有打MappedType這個註解,這個註解是表明這個類對應處理的JavaType是啥。我們這邊沒有找到,因此繼續往下走。
從Mybatis3.1.0開始,會自動解析這個類對應的Java型別,還記得之前我們繼承的BigDecimalTypeHandler中我們的基類BaseTypeHandler繼承了TypeReference麼?
這個類的建構式會獲取泛型中具體的型別是什麼,細節程式碼可以私下看一下。 獲取到了具體的Java型別,我們就繼續往下傳。
因為我們的subBigDecimalTypeHandler是打了MappedJdbcType註解的,因此之後的步驟和register(String.class, JdbcType.NCHAR, new NStringTypeHandler())是一致的,可以回看上文。
到這裡,TypeHandler的註冊部分已經完成了。
在之前的關於對映的文章中,我們提過,Mybatis完成對映後,會選擇合適的TypeHandler處理器,完成對Java業務物件的賦值,我們首先找到入口在哪裡。
完成賦值的就是在1,2處,我們這邊用的是自動對映,因此進1看看,具體關於TypeHandler的處理,不會有太大的差異。 在之前的createAutomaticMappings,找到列名後,會找出對應的欄位,首先會判斷是否有對應的TypeHandler。
因為你知道了JDBC的型別,也透過反射知道了Java的型別。
這邊就首先去TYPE_HANDLER_MAP中找已經存在的JDBC-TypeHandler的對映,如果有的話直接取,沒有的話,就預設取null所對應的那個型別。 因為我們知道jdbc的型別是NUMERIC,而且之前註冊的SubBigDecimalTypeHandler對應的JDBC型別是NUMERIC。
因此就取了更匹配的SubBigDecimalTypeHandler。 之後就是呼叫getResult方法,完成值的獲取即可。
總結
本文主要介紹了:
-
什麼是TypeHandler。
-
如何使用TypeHandler。
-
從系統預設的以及自定義的TypeHandler的註冊和獲取的角度,從原始碼層面分析了整個過程。
看完本文有收穫?請轉發分享給更多人
關註「ImportNew」,提升Java技能