Mybatis返回類型為Map時遇到的類型轉(zhuǎn)化的異常問題
一、問題背景
為了滿足通用化的設(shè)計,項目中通過入?yún)碇付ú樵兊臄?shù)據(jù)表并以Map
的形式獲取所有數(shù)據(jù),同時希望所有取得的數(shù)據(jù)都是String
的格式,因此使用List<Map<String, String>>
來存儲取得的數(shù)據(jù)。
Mapper.java
List<Map<String, String>> selectAllByStatement(...)
Mapper.xml
<select id="selectAllByStatement" parameterType="java.lang.String" resultType="java.util.Map"> ... </select>
二、遇到的問題
從數(shù)據(jù)庫中取得的數(shù)據(jù)無法被正常使用↓
List<Map<String, String>> maps = mapper.selectAllByStatement(tableName, request, statement); Double val1 = Double.valueOf(maps.get(0).get("gmv")); // java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.String Double val2 = Double.valueOf(maps.get(0).get("gmv").toString()); // java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.String Double val3 = maps.get(0).get("gmv"); // 編譯報錯
由于gmv
字段在數(shù)據(jù)庫中的數(shù)據(jù)類型是double
結(jié)果導(dǎo)致maps.get(0)
中的數(shù)據(jù),編譯時看上去是Map定義的類型String
,運行時是數(shù)據(jù)庫中對應(yīng)的類型Double
,然后出現(xiàn)以上錯誤。具體錯誤原因沒有細究
三、解決思路
方法一
Mapper中返回類型定義為 Object,即:
List<Map<String, Object>> selectAllByStatement(...)
每次使用的時候,可以toString()
轉(zhuǎn)化再使用
方法二
本文主要討論這種方式
mybatis從數(shù)據(jù)庫中讀取數(shù)據(jù),并以反射的方式生成Object的對象
然后使用對應(yīng)類型的TypeHandler對其進行數(shù)據(jù)類型的轉(zhuǎn)化
因此,我們需要在TypeHandler上做手腳,自定義TypeHandler并將其注冊到TypeHandlerMap中,讓特定的數(shù)據(jù)類型轉(zhuǎn)化走我們的TypeHandler
在此次問題中,我們以gmv
字段為例,
首先邏輯走到 6.1的24行 handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
propertyType
是Object
,jdbcType
是Double.class
但是mybatis沒有定義Object
→ Double
的handler
所以會走到 6.1的32行 handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
javaType
是從Double
,jdbcType
是Double.class
然后會匹配到org.apache.ibatis.type.DoubleTypeHandler
,把數(shù)據(jù)轉(zhuǎn)換成Double
所以,我們該怎么做呢,我們可以定義一個Object
→ Double
的Handler,讓他在6.1的24行的時候匹配到,里面轉(zhuǎn)化的邏輯我們定義成Object.toString()
,具體操作看《五、具體實現(xiàn)》
四、mybatis處理查詢結(jié)果的源碼
本節(jié)是對mybatis查詢數(shù)據(jù)時,對查詢結(jié)果的處理過程的個人理解
1、查詢
@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 執(zhí)行查詢 ps.execute(); // 處理查詢結(jié)果 --> 2.1 return resultSetHandler.handleResultSets(ps); }
2、準備處理查詢結(jié)果
2.0.1
public class ResultSetWrapper { private final ResultSet resultSet; // 保存著原始數(shù)據(jù) private final TypeHandlerRegistry typeHandlerRegistry; private final List<String> columnNames = new ArrayList<>(); // 數(shù)據(jù)庫列名 private final List<String> classNames = new ArrayList<>(); // 對應(yīng)的Java類型 private final List<JdbcType> jdbcTypes = new ArrayList<>(); // 對應(yīng)的數(shù)據(jù)庫類型 private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<>(); private final Map<String, List<String>> mappedColumnNamesMap = new HashMap<>(); private final Map<String, List<String>> unMappedColumnNamesMap = new HashMap<>(); }
2.1 org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValuesForSimpleResultMap
逐行處理并存儲數(shù)據(jù)
// rsw 保存著查詢結(jié)果的各種相關(guān)數(shù)據(jù) --> 2.0.1 private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { DefaultResultContext<Object> resultContext = new DefaultResultContext<>(); ResultSet resultSet = rsw.getResultSet(); skipRows(resultSet, rowBounds); // 一行行處理 while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) { ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null); // 1. 處理:數(shù)據(jù)對象的生成、類型轉(zhuǎn)化等等 Object rowValue = getRowValue(rsw, discriminatedResultMap, null); // 2. 存儲處理的結(jié)果 storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); } }
3、每一行的處理
解析數(shù)據(jù)庫中取得的數(shù)據(jù)rsw
,反射生成Java的類型
3.0.1
public class MetaObject { private final Object originalObject; // 指向數(shù)據(jù)對象 private final ObjectWrapper objectWrapper; private final ObjectFactory objectFactory; private final ObjectWrapperFactory objectWrapperFactory; private final ReflectorFactory reflectorFactory; }
3.1 將數(shù)據(jù)庫「一行」數(shù)據(jù)轉(zhuǎn)化為對象,并逐字段解析成對應(yīng)的數(shù)據(jù)類型
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); // 1. 創(chuàng)建結(jié)果對象map - 數(shù)據(jù)實例化BaseTypeHandler#getNullableResult Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); if (rowValue != nmonomialull && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { // 2. 創(chuàng)建MetaObject對象,「并將成員變量originalObject指向rowValue」,之后在操作直接對metaObject進行操作; MetaObject --> 3.0.2 final MetaObject metaObject = configuration.newMetaObject(rowValue); boolean foundValues = this.useConstructorMappings; // 3. 是否應(yīng)用自動映射,也就是通過resultType進行映射 if (shouldApplyAutomaticMappings(resultMap, false)) { // 3.1 主要處理流程 --> 4.1 foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues; } foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues; foundValues = lazyLoader.size() > 0 || foundValues; rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; } // 解析完后返回metaObject.originalObject return rowValue; }
4、數(shù)據(jù)類型自動映射
4.0.1
private static class UnMappedColumnAutoMapping { private final String column; // 列名 private final String property; // 屬性名 private final TypeHandler<?> typeHandler; // 該列數(shù)據(jù)的數(shù)據(jù)類型轉(zhuǎn)化Handler private final boolean primitive; }
4.1 為每一列字段找到他的轉(zhuǎn)換Handler
- 通過Handler反射生成他們在Java中的類型
- 將反射生成的對象放入
metaObject.originalObject
中
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException { // 包含每一列數(shù)據(jù)類型對應(yīng)的轉(zhuǎn)化Handler --> 5.1 List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix); boolean foundValues = false; if (!autoMapping.isEmpty()) { for (UnMappedColumnAutoMapping mapping : autoMapping) { // 數(shù)據(jù)轉(zhuǎn)換;表面為Object實際為MySQL對應(yīng)的類型 final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column); if (value != null) { foundValues = true; } if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) { // 將反射生成的對象放入metaObject.originalObject中 metaObject.setValue(mapping.property, value); } } } return foundValues; }
5、為每個字段匹配轉(zhuǎn)換Handler
5.1 得到每個字段的對應(yīng)的Handler
的List(Handler
包在UnMappedColumnAutoMapping
里)
private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException { autoMapping = new ArrayList<>(); final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix); // 1. 對于每一列 分別去找他們的 Handler for (String columnName : unmappedColumnNames) { //... if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) { // 得到該類型 的 TypeHandler。 // 優(yōu)先用屬性類型property type匹配`TypeHandler`,如果沒有再用column JDBC Type去匹配 final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName); autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive())); } //... } return autoMapping; }
6、字段匹配Handler
6.1 優(yōu)先用屬性類型property type匹配TypeHandler
,如果沒有再用column JDBC Type去匹配
public class ResultSetWrapper { private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<>(); // 緩存 /** * Gets the type handler to use when reading the result set. * Tries to get from the TypeHandlerRegistry by searching for the property type. * If not found it gets the column JDBC type and tries to get a handler for it. */ public TypeHandler<?> getTypeHandler(Class<?> propertyType, String columnName) { TypeHandler<?> handler = null; // 先找緩存 // typeHandlerMap 作為緩存 Map<Class<?>, TypeHandler<?>> columnHandlers = typeHandlerMap.get(columnName); // Map<String, Map<Class<?>, TypeHandler<?>>> if (columnHandlers == null) { columnHandlers = new HashMap<>(); typeHandlerMap.put(columnName, columnHandlers); } else { handler = columnHandlers.get(propertyType); } // 沒有緩存的話↓ if (handler == null) { // 獲得列名對應(yīng)的數(shù)據(jù)庫中的類型JdbcType JdbcType jdbcType = getJdbcType(columnName); // 數(shù)據(jù)庫中的類型 // ** 用屬性類型propertyType去找 ** --> 7.1 handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType); // ↑ 用屬性類型propertyType沒找到的話 就 ↓ if (handler == null || handler instanceof UnknownTypeHandler) { // 找該列在數(shù)據(jù)庫的實際類型在Java中對應(yīng)的javaType final int index = columnNames.indexOf(columnName); final Class<?> javaType = resolveClass(classNames.get(index)); if (javaType != null && jdbcType != null) { // ** 用數(shù)據(jù)庫類型去找 ** --> 7.1 handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType); } // ... } if (handler == null || handler instanceof UnknownTypeHandler) { handler = new ObjectTypeHandler(); } // 緩存記錄 columnHandlers.put(propertyType, handler); } return handler; } }
7、給定Type和jdbcType匹配Handler
7.1 org.apache.ibatis.type.TypeHandlerRegistry#getTypeHandler
根據(jù)type和jdbcType一起查符合的Handler
一個propertyType會對應(yīng)多個jdbcType的Handler
所以存儲Handler的變量類型是 Map<Type, Map<JdbcType, TypeHandler<?>>>
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) { if (ParamMap.class.equals(type)) { return null; } /** * 一個propertyType會對應(yīng)多個jdbcType的Handler * 所以存儲Handler的變量類型是 Map<Type, Map<JdbcType, TypeHandler<?>>> **/ // 先根據(jù)propertyType去找 --> 7.2 Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type); TypeHandler<?> handler = null; if (jdbcHandlerMap != null) { // 找到之后,第二層Map再用jdbcType去找 handler = jdbcHandlerMap.get(jdbcType); if (handler == null) { // 如果沒找到走默認的,UnknownTypeHandler handler = jdbcHandlerMap.get(null); } if (handler == null) { // #591 handler = pickSoleHandler(jdbcHandlerMap); } } // type drives generics here return (TypeHandler<T>) handler; }
7.2 給定Java中的類型,查找對應(yīng)的Handler
// 給定Java中的類型,查找對應(yīng)的Handler private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) { Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(type); // typeHandlerMap在初始化時,JDBC Handler被手動注冊上的 --> 8.1 //... return jdbcHandlerMap; }
8、Handler的初始化
8.1 org.apache.ibatis.type.TypeHandlerRegistry
TypeHandlerRegistry
的構(gòu)造函數(shù)會把所有的類型和他對應(yīng)的Handler都注冊到Map上
public final class TypeHandlerRegistry { // 儲存所有的Handler private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>(); public TypeHandlerRegistry() { register(Boolean.class, new BooleanTypeHandler()); register(boolean.class, new BooleanTypeHandler()); register(JdbcType.BOOLEAN, new BooleanTypeHandler()); register(JdbcType.BIT, new BooleanTypeHandler()); register(Integer.class, new IntegerTypeHandler()); register(int.class, new IntegerTypeHandler()); register(JdbcType.INTEGER, new IntegerTypeHandler()); //... 把所有常規(guī)的映射情況都注冊上了 register(Reader.class, new ClobReaderTypeHandler()); register(String.class, new StringTypeHandler()); register(String.class, JdbcType.CHAR, new StringTypeHandler()); register(String.class, JdbcType.CLOB, new ClobTypeHandler()); register(String.class, JdbcType.VARCHAR, new StringTypeHandler()); register(String.class, JdbcType.LONGVARCHAR, new StringTypeHandler()); } 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<>(); typeHandlerMap.put(javaType, map); // typeHandlerMap: Map<Type, Map<JdbcType, TypeHandler<?>>> } map.put(jdbcType, handler); } allTypeHandlersMap.put(handler.getClass(), handler); } }
五、具體實現(xiàn)
實現(xiàn)Handler
自定義TypeHanlder需要繼承BaseTypeHandler
用@MappedJdbcTypes
,@MappedTypes
來定義此Handler應(yīng)用于哪些類型的轉(zhuǎn)換
@MappedTypes
指定字段在Java的數(shù)據(jù)類型
@MappedJdbcTypes
指定字段在數(shù)據(jù)庫中的類型
import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.MappedJdbcTypes; import org.apache.ibatis.type.MappedTypes; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Date; @MappedTypes({Object.class}) @MappedJdbcTypes({JdbcType.DOUBLE, JdbcType.DATE, JdbcType.BIGINT, JdbcType.TIMESTAMP, JdbcType.INTEGER}) public class MapToStringTypeHandler extends BaseTypeHandler<Object> { @Override public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException { if (parameter instanceof Date){ ps.setDate(i, new java.sql.Date(((Date)parameter).getTime())); } else { ps.setString(i, parameter.toString()); } } @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { return rs.getString(columnName); } @Override public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return rs.getString(columnIndex); } @Override public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return cs.getString(columnIndex); } }
配置Handler
在Mybatis 的配置類中指定自定義Handler的包
com.wujie.pandora.repository.config.DataAnalysisMybatisConfig#buildSqlSessionFactory
public SqlSessionFactory buildSqlSessionFactory() { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); //... // 指定自定義Handler的包 bean.setTypeHandlersPackage("com.wujie.pandora.repository.handler"); return bean.getObject(); }
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
在Springboot中Mybatis與Mybatis-plus的區(qū)別詳解
MyBatis是一個優(yōu)秀的持久層框架,它對JDBC的操作數(shù)據(jù)庫的過程進行封裝,MyBatisPlus (簡稱 MP)是一個 MyBatis的增強工具,在 MyBatis 的基礎(chǔ)上只做增強不做改變,為簡化開發(fā)、提高效率而生,本文將給大家介紹了在Springboot中Mybatis與Mybatis-plus的區(qū)別2023-12-12springmvc中進行數(shù)據(jù)保存以及日期參數(shù)的保存過程解析
這篇文章主要介紹了springmvc中進行數(shù)據(jù)保存以及日期參數(shù)的保存過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-09-09