【Mybatis系列】從源碼角度理解Mybatis的數據轉換器TypeHandler

TypeHandlers

不管是 MyBatis 在預處理語句(PreparedStatement)中設置一個參數時,仍是從結果集中取出一個值時, 都會用類型處理器將獲取的值以合適的方式轉換成 Java 類型。java

下面是常見的一些對應類型:
對應類型示例app

以BigDecimalTypeHandler看一下,它主要完成了哪些工做。
BigDecimalTypeHandler函數

這個類的第一個方法是對預處理語句(PreparedStatement)設置參數,以後的三個函數都是從ResultSet或者用於執行存儲過程的CallableStatement語句中獲取BigDecimal類型的數值,用於向BigDecimal類型的Java字段賦值。
BigDecimalTypeHandler繼承的BaseTypeHandler是個泛型類,其餘的TypeHandler也是經過繼承這個抽象類,實現其中的抽象方法,實現類型轉換的工做。
輸入圖片說明工具

這個抽象類實現了TypeHandler接口,這個接口主要定義了類型轉換的幾種操做。
輸入圖片說明ui

至於這個抽象類繼承的TypeReference<T>,主要是提供了獲取這個T具體是哪一個類型。在判斷使用使用哪一個TypeHandler時有用,後文會看到。
輸入圖片說明spa


如何使用

大體介紹了TypeHandler的做用,及其相關類,咱們來看看如何使用它。
今天遇到的主要是從SqlServer中取數據,遇到不少列都是Numeric(10,2)類型,指的是字段是數字型,長度爲10,小數爲兩位。Mybatis默認的BigDecimalTypeHandler取到後,都默認變成4位小數,不夠的補了0。而上層的要求是,拿到的和數字相關的數據都要2位小數。3d

有兩種作法,一種是在全部給上層賦值的時候,都人工對BigDeciam的數據作以下操做。code

setScale(2, BigDecimal.ROUND_HALF_UP)

由於這是一個全局性的要求,全部相關的地方,都須要有這個代碼,雖然能夠寫一個工具類,各個地方調用,但就對本來間接的代碼形成了侵入。既然這樣,爲何不試試TypeHandler。xml

個人作法是繼承BigDecimalTypeHandler,覆蓋原來的取值方法,對取到的數值作範圍限定。
對象

加上@MappedJdbcTypes註解是爲了代表這個類是用於映射JdbcType的NUMERIC類型,這會覆蓋默認的用於轉換Java BegDecimal和Jdbc NUMERIC的BigDecimal,在後面源碼中可略窺一二。

開發完這個轉換類後,你須要在Mybatis的配置文件中聲明這個TypeHandler,這樣Mybatis才知道你本身聲明瞭一個TypeHandler。

<typeHandlers>
    <typeHandler handler="com.codelab.learn.SubBigDecimalTypeHandler"/>
</typeHandlers>

這樣TypeHandler就起做用了。下面是先後效果。
輸入圖片說明
輸入圖片說明


源碼層面

首先Mybatis有一個默認的TypeHandler實現,這些TypeHandler是如何被Mybatis識別的呢。
答案是TypeHandlerRegistry。在Mybatis初始化配置的時候,TypeHandlerRegistry會把JdbcType和Java類型對應的映射關係註冊進該類內部的Map中。
輸入圖片說明
輸入圖片說明
JDBC_TYPE_HANDLER_MAP中記錄的是JdbcType和TypeHandler對應的關係。
12
TYPE_HANDLER_MAP中記錄的是Java類型和對應的全部JdbcType以及其對應TypeHandler的映射關係關係。
13
UNKNOWN_TYPE_HANDLER是在執行BaseTypeHandler的抽象方法時,去先解析出來該用什麼TypeHandler,目前還沒用到,先不研究。
ALL_TYPE_HANDLERS_MAP中記錄的是全部TypeHandler的Class和其實例之間的映射關係。

咱們以系統默認註冊的三個做爲例子,看看整個執行的流程

1 register(String.class, new StringTypeHandler());
2 register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
3 register(JdbcType.NCHAR, new NStringTypeHandler());

第一個是告訴 String類型的轉換,要用StringTypeHandler。
直接進的這個函數,由於咱們的TypeHandler上並無打註解,所以直接進入箭頭標記的邏輯。
輸入圖片說明

而後繼續註冊,只不過jdbcType是null。
輸入圖片說明

後續的代碼比較簡單,會先從Type_HANLER_MAP中看是否有已經存在的 Map<JdbcType, TypeHandler<?>> map,沒有的話,新建,而且放入TYPE_HANDLER_MAP中,在Map<JdbcType, TypeHandler<?>> map放入這次的jdbcType和它對應的Handler。最後在ALL_TYPE_HANDLERS_MAP放入handler的類和實例。

第二個,傳入了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方法,完成值的獲取便可。


總結

本文主要介紹了

  1. 什麼是TypeHandler。
  2. 如何使用TypeHandler。
  3. 從系統默認的以及自定義的TypeHandler的註冊和獲取的角度,從源碼層面分析了整個過程。

但願獲得您的點贊,打賞支持,謝謝。
輸入圖片說明

相關文章
相關標籤/搜索