TypeHandlers
無論是 MyBatis 在預(yù)處理語句(PreparedStatement)中設(shè)置一個(gè)參數(shù)時(shí)姻蚓,還是從結(jié)果集中取出一個(gè)值時(shí), 都會(huì)用類型處理器將獲取的值以合適的方式轉(zhuǎn)換成 Java 類型抖拴。
下面是常見的一些對(duì)應(yīng)類型:
以BigDecimalTypeHandler看一下佣谐,它主要完成了哪些工作规伐。
這個(gè)類的第一個(gè)方法是對(duì)預(yù)處理語句(PreparedStatement)設(shè)置參數(shù)做盅,之后的三個(gè)函數(shù)都是從ResultSet或者用于執(zhí)行存儲(chǔ)過程的CallableStatement語句中獲取BigDecimal類型的數(shù)值,用于向BigDecimal類型的Java字段賦值分扎。
BigDecimalTypeHandler繼承的BaseTypeHandler是個(gè)泛型類澄成,其他的TypeHandler也是通過繼承這個(gè)抽象類,實(shí)現(xiàn)其中的抽象方法,實(shí)現(xiàn)類型轉(zhuǎn)換的工作墨状。
這個(gè)抽象類實(shí)現(xiàn)了TypeHandler接口卫漫,這個(gè)接口主要定義了類型轉(zhuǎn)換的幾種操作。
至于這個(gè)抽象類繼承的TypeReference<T>肾砂,主要是提供了獲取這個(gè)T具體是哪個(gè)類型列赎。在判斷使用使用哪個(gè)TypeHandler時(shí)有用,后文會(huì)看到镐确。
如何使用
大致介紹了TypeHandler的作用包吝,及其相關(guān)類,我們來看看如何使用它源葫。
今天遇到的主要是從SqlServer中取數(shù)據(jù)诗越,遇到很多列都是Numeric(10,2)類型,指的是字段是數(shù)字型,長度為10,小數(shù)為兩位息堂。Mybatis默認(rèn)的BigDecimalTypeHandler取到后嚷狞,都默認(rèn)變成4位小數(shù),不夠的補(bǔ)了0荣堰。而上層的要求是床未,拿到的和數(shù)字相關(guān)的數(shù)據(jù)都要2位小數(shù)。
有兩種做法振坚,一種是在所有給上層賦值的時(shí)候薇搁,都人工對(duì)BigDeciam的數(shù)據(jù)做如下操作。
setScale(2, BigDecimal.ROUND_HALF_UP)
因?yàn)檫@是一個(gè)全局性的要求渡八,所有相關(guān)的地方只酥,都需要有這個(gè)代碼,雖然可以寫一個(gè)工具類呀狼,各個(gè)地方調(diào)用,但就對(duì)原本間接的代碼造成了侵入损离。既然這樣哥艇,為什么不試試TypeHandler。
我的做法是繼承BigDecimalTypeHandler僻澎,覆蓋原來的取值方法貌踏,對(duì)取到的數(shù)值做范圍限定。
加上@MappedJdbcTypes注解是為了表明這個(gè)類是用于映射JdbcType的NUMERIC類型窟勃,這會(huì)覆蓋默認(rèn)的用于轉(zhuǎn)換Java BegDecimal和Jdbc NUMERIC的BigDecimal祖乳,在后面源碼中可略窺一二。
開發(fā)完這個(gè)轉(zhuǎn)換類后秉氧,你需要在Mybatis的配置文件中聲明這個(gè)TypeHandler眷昆,這樣Mybatis才知道你自己聲明了一個(gè)TypeHandler。
<typeHandlers>
<typeHandler handler="com.codelab.learn.SubBigDecimalTypeHandler"/>
</typeHandlers>
這樣TypeHandler就起作用了。下面是前后效果亚斋。
源碼層面
首先Mybatis有一個(gè)默認(rèn)的TypeHandler實(shí)現(xiàn)作媚,這些TypeHandler是如何被Mybatis識(shí)別的呢。
答案是TypeHandlerRegistry帅刊。在Mybatis初始化配置的時(shí)候纸泡,TypeHandlerRegistry會(huì)把JdbcType和Java類型對(duì)應(yīng)的映射關(guān)系注冊(cè)進(jìn)該類內(nèi)部的Map中。
JDBC_TYPE_HANDLER_MAP中記錄的是JdbcType和TypeHandler對(duì)應(yīng)的關(guān)系赖瞒。
TYPE_HANDLER_MAP中記錄的是Java類型和對(duì)應(yīng)的所有JdbcType以及其對(duì)應(yīng)TypeHandler的映射關(guān)系關(guān)系女揭。
UNKNOWN_TYPE_HANDLER是在執(zhí)行BaseTypeHandler的抽象方法時(shí),去先解析出來該用什么TypeHandler栏饮,目前還沒用到吧兔,先不研究。
ALL_TYPE_HANDLERS_MAP中記錄的是所有TypeHandler的Class和其實(shí)例之間的映射關(guān)系抡爹。
我們以系統(tǒng)默認(rèn)注冊(cè)的三個(gè)作為例子掩驱,看看整個(gè)執(zhí)行的流程
1 register(String.class, new StringTypeHandler());
2 register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
3 register(JdbcType.NCHAR, new NStringTypeHandler());
第一個(gè)是告訴 String類型的轉(zhuǎn)換,要用StringTypeHandler冬竟。
直接進(jìn)的這個(gè)函數(shù)欧穴,因?yàn)槲覀兊腡ypeHandler上并沒有打注解,因此直接進(jìn)入箭頭標(biāo)記的邏輯泵殴。
然后繼續(xù)注冊(cè)涮帘,只不過jdbcType是null。
后續(xù)的代碼比較簡單笑诅,會(huì)先從Type_HANLER_MAP中看是否有已經(jīng)存在的 Map<JdbcType, TypeHandler<?>> map调缨,沒有的話,新建吆你,并且放入TYPE_HANDLER_MAP中弦叶,在Map<JdbcType, TypeHandler<?>> map放入此次的jdbcType和它對(duì)應(yīng)的Handler。最后在ALL_TYPE_HANDLERS_MAP放入handler的類和實(shí)例妇多。
第二個(gè)伤哺,傳入了jdbcType是NCHAR,和第一個(gè)類似者祖,但直接就進(jìn)入了最后一步的注冊(cè)環(huán)節(jié)立莉,沒有去判斷傳入什么樣的jdbcType類型,因?yàn)橐呀?jīng)指定了七问。
第三個(gè)是綁定了 jdbcType和Handler之間的對(duì)應(yīng)關(guān)系蜓耻。
OK,前面是系統(tǒng)默認(rèn)注冊(cè)進(jìn)去的械巡,那我們看一下我們?cè)谌绾问褂谜鹿?jié)中添加進(jìn)去的SubBigDecimalTypeHandler是如何被注冊(cè)進(jìn)去的呢刹淌。
Mybatis在應(yīng)用中啟動(dòng)時(shí)饶氏,會(huì)根據(jù)XML文件初始化配置,負(fù)責(zé)解析XML生成配置類的就是XMLConfigBuilder芦鳍,通過調(diào)用其中的parseConfiguration方法填充配置類嚷往。
箭頭表示處,就是解析typehandlers節(jié)點(diǎn)柠衅,我們看看他具體做了些什么皮仁。
因?yàn)槲覀儾皇菍?duì)整個(gè)package進(jìn)行注冊(cè),所以進(jìn)入else分支菲宴,因?yàn)橹槐砻髁艘粋€(gè)最簡單的Handler贷祈,所以要獲取的字段都為null,由此我們也可以看出喝峦,在編寫XML時(shí)势誊,我們也是可以直接指定映射關(guān)系的,因?yàn)楂@取不到j(luò)avaType和jdbcType谣蠢,后面應(yīng)該是會(huì)根據(jù)這個(gè)類再解析一波粟耻。跟注冊(cè)相關(guān)的又回到了TypeHandlerRegistry這個(gè)類里面,職責(zé)還是很清晰的眉踱。
在這個(gè)方法里面挤忙,首先會(huì)獲取有沒有打MappedType這個(gè)注解,這個(gè)注解是表明這個(gè)類對(duì)應(yīng)處理的JavaType是啥谈喳。我們這邊沒有找到册烈,因此繼續(xù)往下走。
從Mybatis3.1.0開始婿禽,會(huì)自動(dòng)解析這個(gè)類對(duì)應(yīng)的Java類型赏僧,還記得之前我們繼承的BigDecimalTypeHandler中我們的基類BaseTypeHandler繼承了TypeReference么?
這個(gè)類的構(gòu)造函數(shù)會(huì)獲取泛型中具體的類型是什么,細(xì)節(jié)代碼可以私下看一下扭倾。
獲取到了具體的Java類型淀零,我們就繼續(xù)往下傳。
因?yàn)槲覀兊膕ubBigDecimalTypeHandler是打了MappedJdbcType注解的膛壹,因此之后的步驟和register(String.class, JdbcType.NCHAR, new NStringTypeHandler())是一致的窑滞,可以回看上文。
到這里恢筝,TypeHandler的注冊(cè)部分已經(jīng)完成了。
在之前的關(guān)于映射的文章中巨坊,我們提過撬槽,Mybatis完成映射后,會(huì)選擇合適的TypeHandler處理器趾撵,完成對(duì)Java業(yè)務(wù)對(duì)象的賦值侄柔,我們首先找到入口在哪里共啃。
完成賦值的就是在1,2處暂题,我們這邊用的是自動(dòng)映射移剪,因此進(jìn)1看看,具體關(guān)于TypeHandler的處理薪者,不會(huì)有太大的差異纵苛。
在之前的createAutomaticMappings,找到列名后言津,會(huì)找出對(duì)應(yīng)的字段攻人,首先會(huì)判斷是否有對(duì)應(yīng)的TypeHandler。
因?yàn)槟阒懒薐DBC的類型悬槽,也通過反射知道了Java的類型怀吻。
這邊就首先去TYPE_HANDLER_MAP中找已經(jīng)存在的JDBC-TypeHandler的映射,如果有的話直接取初婆,沒有的話蓬坡,就默認(rèn)取null所對(duì)應(yīng)的那個(gè)類型。
因?yàn)槲覀冎纉dbc的類型是NUMERIC磅叛,而且之前注冊(cè)的SubBigDecimalTypeHandler對(duì)應(yīng)的JDBC類型是NUMERIC屑咳。
因此就取了更匹配的SubBigDecimalTypeHandler。
之后就是調(diào)用getResult方法宪躯,完成值的獲取即可乔宿。
總結(jié)
本文主要介紹了
- 什么是TypeHandler。
- 如何使用TypeHandler访雪。
- 從系統(tǒng)默認(rèn)的以及自定義的TypeHandler的注冊(cè)和獲取的角度详瑞,從源碼層面分析了整個(gè)過程。
希望得到您的點(diǎn)贊臣缀,打賞支持坝橡,謝謝。