Mybatis中TypeHandler的底層實現
TypeHandler的用法
TypeHandler是Mybatis提供的一個用於使用者自定義處理資料庫型別和java型別之間相互對映的一種工具,它可以實現jdbcType到javaType的自動轉換。
要實現自定義的TypeHandler首先需要繼承BaseTypeHandler,然後在類上新增@MappedTypes
@MappedTypes({String.class})
public MyTypeHandler extends BaseTypeHandler<T> {
}
然後在Mybatis的mapper.xml檔案中使用自定義的MyTypeHandler
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into user (username) values (#{username,jdbcType=VARCHAR,javaType=String,typeHandler=MyTypeHandler})
</insert>
TypeHandler的原理
TypeHandler是Mybatis提供的一個介面,具體方法如下
public interface TypeHandler<T> { //設定引數 void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; //取得結果,供普通select用 T getResult(ResultSet rs, String columnName) throws SQLException; //取得結果,供普通select用 T getResult(ResultSet rs, int columnIndex) throws SQLException; //取得結果,供SP用 T getResult(CallableStatement cs, int columnIndex) throws SQLException; }
在TypeHandlerRegistry.java檔案中有兩個屬性用來放置Mybatis自帶或者是使用者自定義的TypeHandler。其中JDBC_TYPE_HANDLER_MAP用來表示JdbcType和TypeHandler的對應關係,而TYPE_HANDLER_MAP則用來表示Java類與JdbcType和TypeHandler的關係
這兩者的區別是一個用來根據JdbcType來獲取相應的TypeHandler,一個是根據JavaType和JdbcType來獲取相應的TypeHandler,具體的使用方法可以在ResultSetWrapper.java中的getTypeHandler()方法中可以看到
從第112行開始可以看到一共分為三種情況,分別是
- javaType存在和jdbcType存在
- javaType不存在
- jdbcType不存在
具體的程式碼可以到TypeHandlerRegistry類的getTypeHandler()方法中看到,根據是否存在會從兩個map中選擇一個進行獲取
在TypeHandlerRegistry的構造器中可以看到在這裡對Mybatis預設實現的一些TypeHandler都會在這裡進行註冊
BaseBuilder.java是用來根據使用者的xml來建立相應的元素,在BaseBuilder.java中的resolveTypeHandler方法既是為了對使用者定義的TypeHandler進行分析並建立相應的對映關係,在這個方法中可以看到,當傳入的typeHandlerType還沒有被生成過,就對相應javaType對應的typeHandlerType進行初始化操作
那麼resolveTypeHandler()這個方法是在哪裡呼叫的呢,答案就是在SqlSourceBuilder.java的buildParameterMapping()方法中,在這裡會對所有使用者自定義的元素進行解析,當然也包括定義的TypeHandler
初始化操作在TypeHandlerRegistry.java中的getInstance()方法中進行,通過反射取得相應TypeHandler的構造器來生成新的自定義TypeHandler物件
有關TypeHandler是單例還是多例的問題
根據上面的分析可以看到所有的TypeHandler都會被Mybatis儲存到一個Map中,每次需要的時候都從中獲取,所以從這一點來看毫無疑問TypeHandler是單例的,但是對於如下情況
@MappedTypes({Server.class, Clien.class})
public MyTypeHandler extends BaseTypeHandler<T> {
Class clazz;
public MyTypeHandler(Class clazz) {
//構造器
this.clazz = clazz;
}
}
當我們在@MappedTypes標籤中定義多個class時,在這種情況下,由於Mybatis是以javaType作為Map的鍵,所以對於多個使用者自定義java型別會多次進行對MyTypeHandler的構造,所以會多次進入MyTypeHandler的構造器,如果我們通過將構造器中傳入的型別進行儲存,則會發現每次傳入的clazz的值都是不一樣的,Mybatis會使用遍歷的方法把每一個註解中的型別都傳入一次進行構造,從這一點來看錶現則像是多例的,我們可以得到很多clazz屬性不同的MyTypeHandler類,但是每一種仍然只會生成一次,原因如上所說,所以我們可以利用這一點將傳入的clazz儲存起來實現一個通用的自定義TypeHandler來對一些型別進行整合,比如使用TypeHandler進行JSON物件和Java物件的轉換,而不用為每一個Java物件都實現一遍自定義的TypeHandler。