1. 程式人生 > >Mybatis中TypeHandler的底層實現

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。