MyBatis類(lèi)型轉(zhuǎn)換模塊的實(shí)現(xiàn)
前言
MyBatis是一個(gè)持久層框架ORM框架,實(shí)現(xiàn)數(shù)據(jù)庫(kù)中數(shù)據(jù)和Java對(duì)象中的屬性的雙向映射,那么不可避免的就會(huì)碰到類(lèi)型轉(zhuǎn)換的問(wèn)題,在PreparedStatement為SQL語(yǔ)句綁定參數(shù)時(shí),需要從Java類(lèi)型轉(zhuǎn)換為JDBC類(lèi)型,而從結(jié)果集中獲取數(shù)據(jù)時(shí),則需要從JDBC類(lèi)型轉(zhuǎn)換為Java類(lèi)型,所以本文來(lái)看下在MyBatis中是如何實(shí)現(xiàn)類(lèi)型的轉(zhuǎn)換的。
TypeHandler
MyBatis中的所有的類(lèi)型轉(zhuǎn)換器都繼承了TypeHandler接口,在TypeHandler中定義了類(lèi)型轉(zhuǎn)換器的最基本的功能。
/** * @author Clinton Begin */ public interface TypeHandler<T> { /** * 負(fù)責(zé)將Java類(lèi)型轉(zhuǎn)換為JDBC的類(lèi)型 * 本質(zhì)上執(zhí)行的就是JDBC操作中的 如下操作 * String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = ? and user_name = ?"; * ps = conn.prepareStatement(sql); * ps.setInt(1,2); * ps.setString(2,"張三"); * @param ps * @param i 對(duì)應(yīng)占位符的 位置 * @param parameter 占位符對(duì)應(yīng)的值 * @param jdbcType 對(duì)應(yīng)的 jdbcType 類(lèi)型 * @throws SQLException */ void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; /** * 從ResultSet中獲取數(shù)據(jù)時(shí)會(huì)調(diào)用此方法,會(huì)將數(shù)據(jù)由JdbcType轉(zhuǎn)換為Java類(lèi)型 * @param columnName Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code> */ T getResult(ResultSet rs, String columnName) throws SQLException; T getResult(ResultSet rs, int columnIndex) throws SQLException; T getResult(CallableStatement cs, int columnIndex) throws SQLException; }
BaseTypeHandler
為了方便用戶(hù)自定義TypeHandler的實(shí)現(xiàn),在MyBatis中提供了BaseTypeHandler這個(gè)抽象類(lèi),它實(shí)現(xiàn)了TypeHandler接口,并繼承了TypeReference類(lèi),
在BaseTypeHandler中的實(shí)現(xiàn)方法中實(shí)現(xiàn)了對(duì)null的處理,非空的處理是交給各個(gè)子類(lèi)去實(shí)現(xiàn)的。這個(gè)在代碼中很清楚的體現(xiàn)了出來(lái)
TypeHandler實(shí)現(xiàn)類(lèi)
TypeHandler的實(shí)現(xiàn)類(lèi)比較多,而且實(shí)現(xiàn)也都比較簡(jiǎn)單。
以Integer為例
/** * @author Clinton Begin */ public class IntegerTypeHandler extends BaseTypeHandler<Integer> { @Override public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType) throws SQLException { ps.setInt(i, parameter); // 實(shí)現(xiàn)參數(shù)的綁定 } @Override public Integer getNullableResult(ResultSet rs, String columnName) throws SQLException { int result = rs.getInt(columnName); // 獲取指定列的值 return result == 0 && rs.wasNull() ? null : result; } @Override public Integer getNullableResult(ResultSet rs, int columnIndex) throws SQLException { int result = rs.getInt(columnIndex); // 獲取指定列的值 return result == 0 && rs.wasNull() ? null : result; } @Override public Integer getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { int result = cs.getInt(columnIndex); // 獲取指定列的值 return result == 0 && cs.wasNull() ? null : result; } }
TypeHandlerRegistry
在MyBatis中給我們提供的具體的類(lèi)型轉(zhuǎn)換器實(shí)在是太多了,那么在實(shí)際的使用時(shí)我們是如何知道使用哪個(gè)轉(zhuǎn)換器類(lèi)處理的呢?實(shí)際上再M(fèi)yBatis中是將所有的TypeHandler都保存注冊(cè)在了TypeHandlerRegistry中的。首先注意聲明的相關(guān)屬性
// 記錄JdbcType和TypeHandle的對(duì)應(yīng)關(guān)系 private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class); // 記錄Java類(lèi)型向指定的JdbcType轉(zhuǎn)換時(shí)需要使用到的TypeHandle private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>(); private final TypeHandler<Object> unknownTypeHandler; // 記錄全部的TypeHandle類(lèi)型及對(duì)應(yīng)的TypeHandle對(duì)象 private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>(); // 空TypeHandle的標(biāo)識(shí) private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
在器構(gòu)造方法中完成了系統(tǒng)提供的TypeHandler的注冊(cè)
有注冊(cè)的方法,當(dāng)然也有從注冊(cè)器中獲取TypeHandler的方法,getTypeHandler方法,這個(gè)方法也有多個(gè)重載的方法,這里重載的方法最終都會(huì)執(zhí)行的方法是
/** * 根據(jù)對(duì)應(yīng)的Java類(lèi)型和Jdbc類(lèi)型來(lái)查找對(duì)應(yīng)的TypeHandle */ private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) { if (ParamMap.class.equals(type)) { return null; } // 根據(jù)Java類(lèi)型獲取對(duì)應(yīng)的 Jdbc類(lèi)型和TypeHandle的集合容器 Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type); TypeHandler<?> handler = null; if (jdbcHandlerMap != null) { // 根據(jù)Jdbc類(lèi)型獲取對(duì)應(yīng)的 處理器 handler = jdbcHandlerMap.get(jdbcType); if (handler == null) { // 獲取null對(duì)應(yīng)的處理器 handler = jdbcHandlerMap.get(null); } if (handler == null) { // #591 handler = pickSoleHandler(jdbcHandlerMap); } } // type drives generics here return (TypeHandler<T>) handler; }
除了使用系統(tǒng)提供的TypeHandler以外,還可以創(chuàng)建自己的TypeHandler了,具體使用見(jiàn)MyBatis 核心配置。
TypeAliasRegistry
在MyBatis的應(yīng)用的時(shí)候會(huì)經(jīng)常用到別名,這能大大簡(jiǎn)化我們的代碼,其實(shí)在MyBatis中是通過(guò)TypeAliasRegistry類(lèi)管理的。
注冊(cè)的方法邏輯也比較簡(jiǎn)單
public void registerAlias(String alias, Class<?> value) { if (alias == null) { throw new TypeException("The parameter alias cannot be null"); } // issue #748 別名統(tǒng)一轉(zhuǎn)換為小寫(xiě) String key = alias.toLowerCase(Locale.ENGLISH); // 檢測(cè)別名是否存在 if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) { throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'."); } // 將 別名 和 類(lèi)型 添加到 Map 集合中 typeAliases.put(key, value); }
TypeHandler的應(yīng)用
SqlSessionFactory
在構(gòu)建SqlSessionFactory時(shí),在Configuration對(duì)象實(shí)例化的時(shí)候在成員變量中完成了TypeHandlerRegistry和TypeAliasRegistry的實(shí)例化
在TypeHandlerRegistry的構(gòu)造方法中完成了常用類(lèi)型的TypeHandler的注冊(cè)
在TypeAliasRegistry中完成了常用Java類(lèi)型別名的注冊(cè)
在Configuration的構(gòu)造方法中會(huì)為各種常用的類(lèi)型向TypeAliasRegistry中注冊(cè)類(lèi)型別名數(shù)據(jù)
以上步驟完成了TypeHandlerRegistry和TypeAliasRegistry的初始化操作
然后在解析全局配置文件時(shí)會(huì)通過(guò)解析<typeAliases>標(biāo)簽和<typeHandlers>標(biāo)簽,可以注冊(cè)我們添加的別名和TypeHandler。
在全局配置文件中指定了對(duì)應(yīng)的別名,那么在映射文件中就可以簡(jiǎn)寫(xiě)類(lèi)型了,這樣在解析映射文件時(shí),我們同樣也是需要做別名的處理的。
這個(gè)parameterType就可以是我們定義的別名,然后在 resolveClass中就會(huì)做對(duì)應(yīng)的處理
protected <T> Class<? extends T> resolveClass(String alias) { if (alias == null) { return null; } try { return resolveAlias(alias); // 別名處理 } catch (Exception e) { throw new BuilderException("Error resolving class. Cause: " + e, e); } } protected <T> Class<? extends T> resolveAlias(String alias) { return typeAliasRegistry.resolveAlias(alias); // 根據(jù)別名查找真實(shí)的類(lèi)型 }
執(zhí)行SQL語(yǔ)句
TypeHandler類(lèi)型處理器使用比較多的地方應(yīng)該是在給SQL語(yǔ)句中參數(shù)綁定值和查詢(xún)結(jié)果和對(duì)象中屬性映射的地方用到的比較多,
DefaultParameterHandler中看看參數(shù)是如何處理的
/** * 為 SQL 語(yǔ)句中的 ? 占位符 綁定實(shí)參 */ @Override public void setParameters(PreparedStatement ps) { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); // 取出SQL中的參數(shù)映射列表 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); // 獲取對(duì)應(yīng)的占位符 if (parameterMappings != null) { for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { // 過(guò)濾掉存儲(chǔ)過(guò)程中的 輸出參數(shù) Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { // value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } // 獲取 參數(shù)類(lèi)型 對(duì)應(yīng)的 類(lèi)型處理器 TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } try { // 通過(guò)TypeHandler 處理參數(shù) typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (TypeException | SQLException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } } }
然后進(jìn)入到DefaultResultSetHandler中的getRowValue方法中
然后再進(jìn)入applyAutomaticMappings方法中查看
可以看到,根據(jù)對(duì)應(yīng)的TypeHandler返回對(duì)應(yīng)類(lèi)型的值。
到此這篇關(guān)于MyBatis類(lèi)型轉(zhuǎn)換模塊的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)MyBatis類(lèi)型轉(zhuǎn)換內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- MybatisPlus字段類(lèi)型轉(zhuǎn)換的實(shí)現(xiàn)示例
- mybatis自定義參數(shù)類(lèi)型轉(zhuǎn)換器數(shù)據(jù)庫(kù)字段加密脫敏
- mybatis-plus?分頁(yè)類(lèi)型轉(zhuǎn)換工具類(lèi)
- 使用?mybatis?自定義日期類(lèi)型轉(zhuǎn)換器的示例代碼
- mybatis類(lèi)型轉(zhuǎn)換器如何實(shí)現(xiàn)數(shù)據(jù)加解密
- Mybatis自定義類(lèi)型轉(zhuǎn)換器的使用技巧
- MyBatis自定義類(lèi)型轉(zhuǎn)換器實(shí)現(xiàn)加解密
- Mybatis實(shí)現(xiàn)自定義類(lèi)型轉(zhuǎn)換器TypeHandler的方法
相關(guān)文章
java中URLEncoder.encode與URLDecoder.decode處理url特殊參數(shù)的方法
這篇文章主要給大家介紹了關(guān)于java中URLEncoder.encode與URLDecoder.decode處理url特殊參數(shù)的方法,文中介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-03-03SpringBoot異步使用@Async的原理以及線(xiàn)程池配置詳解
在項(xiàng)目中當(dāng)訪(fǎng)問(wèn)其他人的接口較慢時(shí),不想程序一直卡在耗時(shí)任務(wù)上,想程序能夠并行執(zhí)行,我們可以使用多線(xiàn)程來(lái)并行的處理任務(wù),也可以使用spring提供的異步處理方式@Async,這篇文章主要給大家介紹了關(guān)于SpringBoot異步使用@Async的原理以及線(xiàn)程池配置的相關(guān)資料2021-09-09關(guān)于Springboot中JSCH的使用及說(shuō)明
這篇文章主要介紹了關(guān)于Springboot中JSCH的使用及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。2022-09-09java實(shí)現(xiàn)PPT轉(zhuǎn)化為PDF
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)PPT轉(zhuǎn)化為PDF的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06java基于GUI實(shí)現(xiàn)簡(jiǎn)單畫(huà)筆小畫(huà)板
這篇文章主要為大家詳細(xì)介紹了java基于GUI實(shí)現(xiàn)簡(jiǎn)單畫(huà)筆小畫(huà)板,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06Spring學(xué)習(xí)筆記3之消息隊(duì)列(rabbitmq)發(fā)送郵件功能
這篇文章主要介紹了Spring學(xué)習(xí)筆記3之消息隊(duì)列(rabbitmq)發(fā)送郵件功能的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07Java后端SSM框架圖片上傳功能實(shí)現(xiàn)方法解析
這篇文章主要介紹了Java后端SSM框架圖片上傳功能實(shí)現(xiàn)方法解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06