Mybatis?TypeHandler接口及繼承關(guān)系示例解析
開(kāi)篇
JDBC類型與Java類型并不是完全一一對(duì)應(yīng)的。所以在PreparedStatement綁定參數(shù)的時(shí)候需要把Java類型轉(zhuǎn)為JDBC類型。JDBC類型的枚舉值在JdbcType
枚舉值中存儲(chǔ)。
MyBatis中提供了一個(gè)接口專用于JDBC類型與Java類型的轉(zhuǎn)換。它就是我們今天的主題:TypeHandler(類型轉(zhuǎn)換器)
TypeHandler接口
TypeHandler是用于JDBC類型與Java類型的轉(zhuǎn)換
我們先來(lái)看一下這個(gè)接口的定義,它都規(guī)范了哪些行為。再來(lái)說(shuō)這些方法的實(shí)現(xiàn)類和具體作用。
public interface TypeHandler<T> { void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; T getResult(ResultSet rs, String columnName) throws SQLException; T getResult(ResultSet rs, int columnIndex) throws SQLException; T getResult(CallableStatement cs, int columnIndex) throws SQLException; }
該接口中共有4個(gè)方法。這四個(gè)方法只定義了兩種行為,一是setParameter(設(shè)置參數(shù))二是操作結(jié)果集獲取指定對(duì)象
方法名 | 描述 |
---|---|
setParameter | 使用PreparedStatement執(zhí)行SQL是,設(shè)置帶? 的參數(shù)。 |
getResult | 獲取結(jié)果集中指定列名對(duì)應(yīng)的值,或者獲取結(jié)果集中指定索引對(duì)應(yīng)的值 |
實(shí)際setParameter方法的底層實(shí)現(xiàn)就是JDBC操作
PreparedStatement ps = connection.prepareStatement(); ps.setString(1,"value");
getResult方法的底層同樣也是JDBC操作
// rs是結(jié)果集對(duì)象ResultSet rs.getInt(colName); rs.getInt(i)
TypeHandler繼承體系
在MyBatis中JDBC類型被定義在枚舉類JdbcType
中。共有41中JDBC類型。每個(gè)JDBC類型都對(duì)應(yīng)一個(gè)XxxTypeHandler
類。每個(gè)類都是解決特定的JDBC類型轉(zhuǎn)換
TypeHandler接口值規(guī)定了行為,每種JDBC類型,都提供了對(duì)應(yīng)的實(shí)現(xiàn)類來(lái)完場(chǎng)JDBC到Java類型的轉(zhuǎn)換。
每個(gè)TypeHandler的實(shí)現(xiàn)都大同小異,而為了避免空指針的問(wèn)題,TypeHandler還有一個(gè)抽象子類,對(duì)這4個(gè)方法做了模板處理。
- 設(shè)置參數(shù):如果值為null則直接跳過(guò),否則調(diào)用具體實(shí)現(xiàn)類設(shè)置參數(shù)
- 從結(jié)果集中獲取數(shù)據(jù):調(diào)用具體實(shí)現(xiàn)類的
getNullableResult
方法
BaseTypeHandler的代碼比較簡(jiǎn)單易懂,這列舉重要實(shí)現(xiàn):
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { if (parameter == null) { if (jdbcType == null) {拋異常} try { ps.setNull(i, jdbcType.TYPE_CODE); } catch (SQLException e) { 拋異常 } } else { try { // 如果參數(shù)不為Null才調(diào)用子類方法 setNonNullParameter(ps, i, parameter, jdbcType); } catch (Exception e) { 拋異常 } } }
我們先來(lái)剖析下常用的類型。IntegerTypeHandler
IntegerTypeHandler
public class IntegerTypeHandler extends BaseTypeHandler<Integer> { @Override public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType) throws SQLException { ps.setInt(i, parameter); } public Integer getNullableResult(ResultSet rs, String columnName) throws SQLException { int result = rs.getInt(columnName); return result == 0 && rs.wasNull() ? null : result; } // 省略另外兩個(gè)方法 }
可以看到IntegerTypeHandler的底層就是調(diào)用了JDBC對(duì)象PreparedStatement的方法來(lái)設(shè)置參數(shù)與獲取結(jié)果集中的記錄。這個(gè)實(shí)現(xiàn)是最簡(jiǎn)單的一個(gè),就不展開(kāi)介紹了。
下面我們?cè)賮?lái)看一下日期類型的映射
DateTypeHandler
public class DateTypeHandler extends BaseTypeHandler<Date> { @Override public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException { ps.setTimestamp(i, new Timestamp(parameter.getTime())); } @Override public Date getNullableResult(ResultSet rs, String columnName) throws SQLException { Timestamp sqlTimestamp = rs.getTimestamp(columnName); if (sqlTimestamp != null) { return new Date(sqlTimestamp.getTime()); } return null; } }
- 設(shè)置參數(shù):我們使用Java編程,使用的日期常用的類是
java.util.Date
,但是在JDBC操作數(shù)據(jù)庫(kù)的時(shí)候,往往需要的不是Date類型,而是SQL標(biāo)準(zhǔn)定義的Timestamp
類型,DateTypeHandler會(huì)幫助我們把Date轉(zhuǎn)化為T(mén)imestamp - 獲取結(jié)果,從JDBC中獲取的記錄如果是Timestamp類型,則DateTypeHandler幫我們轉(zhuǎn)為
java.util.Date
對(duì)象
注:可能大家在編程JDBC的時(shí)候,執(zhí)行如下語(yǔ)句select * from user where birthday > ?
,無(wú)論數(shù)據(jù)庫(kù)中birthday字段是Date/DateTime/TimeStamp類型,我們都會(huì)把會(huì)把?
的值設(shè)置為Date類型,實(shí)際上這是不符合JDBC標(biāo)準(zhǔn)的。
其他TypeHandler的實(shí)現(xiàn),就不再看了。邏輯都是一樣的。
TypeHandlerRegistry
通過(guò)上一小節(jié)了解到了每一種JDBC類型,都提供了TypeHandler的實(shí)現(xiàn)。那么mybatis什么時(shí)候才會(huì)用到這些實(shí)現(xiàn)類呢?或者說(shuō)當(dāng)需要進(jìn)行類型轉(zhuǎn)換的時(shí)候,MyBatis是如何使用這些實(shí)現(xiàn)類的。
在MyBatis初始化的時(shí)候,會(huì)加載TypeHandlerRegistry這個(gè)類,這個(gè)類有在構(gòu)造方法里會(huì)調(diào)用一系列的register
方法把TypeHandler所有實(shí)現(xiàn)類都注冊(cè)到TypeHandlerRegistry中。下面我們先來(lái)看一下TypeHandlerRegistry中的重要屬性
public final class TypeHandlerRegistry { private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class); private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>(); private final TypeHandler<Object> unknownTypeHandler; private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>(); }
- jdbcTypeHandlerMap:key是Jdbc類型,value是Jdbc類型對(duì)應(yīng)的處理器
- typeHandlerMap:key是Java類型,value是對(duì)應(yīng)的處理器集合(它也是要給Map)。因?yàn)橐粋€(gè)Java類型可能被多個(gè)處理器解析。比如String類型可能被解析為char varchar類型等。比如Java的
java.util.Date
類型可以被解析為Date Time TimeStamp類型。存在一對(duì)多的關(guān)系 - unknownTypeHandler:位置的TypeHandler,一般為空
- allTypeHandlersMap:所有類型處理器,key是TypeHandler實(shí)現(xiàn)類的Class對(duì)象,value是具體的TypeHandler
TypeHandlerRegistry#register方法
上面介紹了TypeHandlerRegistry中幾個(gè)重要的屬性。在mybatis啟動(dòng)時(shí),就會(huì)把所有的TypeHandler都注冊(cè)到如上的4個(gè)數(shù)據(jù)結(jié)構(gòu)中。具體的實(shí)現(xiàn)在TypeHandlerRegistry的構(gòu)造方法中,調(diào)用register方法進(jìn)行注冊(cè)TypeHandler
public TypeHandlerRegistry(Configuration configuration) { register(Boolean.class, new BooleanTypeHandler()); register(boolean.class, new BooleanTypeHandler()); register(JdbcType.BOOLEAN, new BooleanTypeHandler()); register(JdbcType.BIT, new BooleanTypeHandler()); register(Byte.class, new ByteTypeHandler()); register(byte.class, new ByteTypeHandler()); register(JdbcType.TINYINT, new ByteTypeHandler()); register(Short.class, new ShortTypeHandler()); register(short.class, new ShortTypeHandler()); register(JdbcType.SMALLINT, new ShortTypeHandler()); // ...省略其他register }
這些register最終將如上的3個(gè)Map數(shù)據(jù)結(jié)構(gòu)填充。通過(guò)名字也能看出來(lái),注冊(cè)Class/JdbcType與TypeHandler的映射關(guān)系。而這些register分為兩大類,一類是注冊(cè)JDBC-TypeHandler映射關(guān)系,一類是注冊(cè)Java-TypeHandler的映射關(guān)系。我們先來(lái)看第一種
public void register(JdbcType jdbcType, TypeHandler<?> handler) { jdbcTypeHandlerMap.put(jdbcType, handler); }
它的實(shí)現(xiàn)很簡(jiǎn)單,就是直接把jdbcType作為key,TypeHandler作為value,存入jdbcTypeHandlerMap即可。接下來(lái)我們?cè)賮?lái)看第二種:注冊(cè)Java-TypeHandler的映射關(guān)系
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) { MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class); if (mappedJdbcTypes != null) { for (JdbcType handledJdbcType : mappedJdbcTypes.value()) { register(javaType, handledJdbcType, typeHandler); } if (mappedJdbcTypes.includeNullJdbcType()) { register(javaType, null, typeHandler); } } else { register(javaType, null, typeHandler); } }
我們來(lái)分析下 注冊(cè)步驟:
- 獲取MappedJdbcTypes注解,對(duì)自定義的TypeHandler進(jìn)行注冊(cè)。(自定義TypeHandler時(shí)需要該注解指定自定義的TypeHandler都解析哪些JDBC類型)
- 如果TypeHandler沒(méi)有注解信息(也就是沒(méi)有自定義TypeHandler)則直接調(diào)用重載register方法注冊(cè)Java-TypeHandler的映射關(guān)系。
下面就來(lái)看一下這個(gè)重載register方法
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) { if (javaType != null) { Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType); if (map == null || map == NULL_TYPE_HANDLER_MAP) { map = new HashMap<>(); } map.put(jdbcType, handler); typeHandlerMap.put(javaType, map); } allTypeHandlersMap.put(handler.getClass(), handler); }
代碼中它主要做了兩件事:
- 注冊(cè)Java-TypeHandler的映射關(guān)系
- 把TypeHandler的Class和類的映射關(guān)系存入allTypeHandlersMap
至此TypeHandlerRegistry中就有了最重要的兩種映射關(guān)系
- JDBC類型 與 TypeHandler 的映射關(guān)系(通過(guò)Jdbc類型尋找合適的TypeHandler類型,通常用于結(jié)果集的解析過(guò)程)
- Java類型 與 TypeHandler 的映射關(guān)系(通過(guò)Java類型尋找合適的TypeHandler類型,通常用于為PreparedStatement對(duì)象設(shè)置參數(shù))
TypeHandlerRegistry也提供了一系列的get*
方法,可以根據(jù)指定的信息返回需要的TypeHandler類
- getMappingTypeHandler(Class>):根據(jù)TypeHandler類型獲取對(duì)應(yīng)的TypeHandler對(duì)象
- getTypeHandler(JdbcType):根據(jù)JdbcType類型獲取TypeHandler對(duì)象
- getTypeHandler(Class):根據(jù)Class類型獲取TypeHandler對(duì)象
- getJdbcHandlerMap(Type):根據(jù)Java類型獲取TypeHandler對(duì)象
總結(jié)
TypeHandler是類型處理器,它用來(lái)解析Java類型與Jdbc類型之間的相互轉(zhuǎn)換。
TypeHandlerRegistry是一個(gè)注冊(cè)器,其中注冊(cè)了JDBC與TypeHandler的映射關(guān)系、Java類型與TypeHandler的映射關(guān)系。
那么由此我們可以想象到,在mybatis執(zhí)行SQL的過(guò)程中,一定會(huì)在某處調(diào)用TypeHandlerRegistry并通過(guò)參數(shù)的Java類型獲取對(duì)應(yīng)的TypeHandler對(duì)象為PreparedStatement設(shè)置參數(shù)。
也一定會(huì)在解析結(jié)果集的過(guò)程中,調(diào)用TypeHandlerRegistry并通過(guò)結(jié)果集數(shù)據(jù)的Jdbc類型獲取對(duì)應(yīng)的TypeHandler對(duì)象為來(lái)解析結(jié)果集中的記錄
以上就是Mybatis TypeHandler接口及繼承關(guān)系示例解析的詳細(xì)內(nèi)容,更多關(guān)于Mybatis TypeHandler接口繼承的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Mybatis的TypeHandler實(shí)現(xiàn)數(shù)據(jù)加解密詳解
- Mybatis基于TypeHandler實(shí)現(xiàn)敏感數(shù)據(jù)加密
- MyBatis中的自定義TypeHandler詳解
- MyBatis?typeHandler接口的定義和使用
- Mybatis之類型處理器TypeHandler的作用與自定義方式
- mybatis之BaseTypeHandler用法解讀
- mybatis-plus之自動(dòng)映射字段(typeHandler)的注意點(diǎn)及說(shuō)明
- Mybatis的類型轉(zhuǎn)換接口TypeHandler
- Mybatis中TypeHandler使用小結(jié)
相關(guān)文章
Java編程中二維數(shù)組的初始化和基本操作實(shí)例
這篇文章主要介紹了Java編程中二維數(shù)組的初始化和基本操作實(shí)例,是Java入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-10-10InputStreamReader 和FileReader的區(qū)別及InputStream和Reader的區(qū)別
這篇文章主要介紹了InputStreamReader 和FileReader的區(qū)別及InputStream和Reader的區(qū)別的相關(guān)資料,需要的朋友可以參考下2015-12-12SpringBoot創(chuàng)建WebService方法詳解
這篇文章主要介紹了SpringBoot如何創(chuàng)建WebService,文中有詳細(xì)的實(shí)現(xiàn)步驟以及示例代碼,對(duì)學(xué)習(xí)或工作有一定的幫助,需要的朋友跟著小編一起來(lái)學(xué)習(xí)吧2023-05-05解析spring事務(wù)管理@Transactional為什么要添加rollbackFor=Exception.class
這篇文章主要介紹了spring事務(wù)管理@Transactional為什么要添加rollbackFor=Exception.class,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-11-11如何將Java對(duì)象轉(zhuǎn)換為JSON實(shí)例詳解
有時(shí)候需要將對(duì)象轉(zhuǎn)換為JSON格式,所以這篇文章主要給大家介紹了關(guān)于如何將Java對(duì)象轉(zhuǎn)換為JSON的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08