Mybatis返回類型為Map時(shí)遇到的類型轉(zhuǎn)化的異常問題
一、問題背景
為了滿足通用化的設(shè)計(jì),項(xiàng)目中通過入?yún)碇付ú樵兊臄?shù)據(jù)表并以Map的形式獲取所有數(shù)據(jù),同時(shí)希望所有取得的數(shù)據(jù)都是String的格式,因此使用List<Map<String, String>>來存儲(chǔ)取得的數(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"); // 編譯報(bào)錯(cuò)
由于gmv字段在數(shù)據(jù)庫中的數(shù)據(jù)類型是double
結(jié)果導(dǎo)致maps.get(0)中的數(shù)據(jù),編譯時(shí)看上去是Map定義的類型String,運(yùn)行時(shí)是數(shù)據(jù)庫中對(duì)應(yīng)的類型Double,然后出現(xiàn)以上錯(cuò)誤。具體錯(cuò)誤原因沒有細(xì)究
三、解決思路
方法一
Mapper中返回類型定義為 Object,即:
List<Map<String, Object>> selectAllByStatement(...)
每次使用的時(shí)候,可以toString()轉(zhuǎn)化再使用
方法二
本文主要討論這種方式
mybatis從數(shù)據(jù)庫中讀取數(shù)據(jù),并以反射的方式生成Object的對(duì)象
然后使用對(duì)應(yīng)類型的TypeHandler對(duì)其進(jìn)行數(shù)據(jù)類型的轉(zhuǎn)化
因此,我們需要在TypeHandler上做手腳,自定義TypeHandler并將其注冊(cè)到TypeHandlerMap中,讓特定的數(shù)據(jù)類型轉(zhuǎn)化走我們的TypeHandler
在此次問題中,我們以gmv字段為例,
首先邏輯走到 6.1的24行 handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
propertyType是Object,jdbcType 是Double.class
但是mybatis沒有定義Object → Double的handler
所以會(huì)走到 6.1的32行 handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
javaType是從Double,jdbcType 是Double.class
然后會(huì)匹配到org.apache.ibatis.type.DoubleTypeHandler,把數(shù)據(jù)轉(zhuǎn)換成Double
所以,我們?cè)撛趺醋瞿?,我們可以定義一個(gè)Object → Double的Handler,讓他在6.1的24行的時(shí)候匹配到,里面轉(zhuǎn)化的邏輯我們定義成Object.toString(),具體操作看《五、具體實(shí)現(xiàn)》
四、mybatis處理查詢結(jié)果的源碼
本節(jié)是對(duì)mybatis查詢數(shù)據(jù)時(shí),對(duì)查詢結(jié)果的處理過程的個(gè)人理解
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、準(zhǔn)備處理查詢結(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<>(); // 對(duì)應(yīng)的Java類型
private final List<JdbcType> jdbcTypes = new ArrayList<>(); // 對(duì)應(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
逐行處理并存儲(chǔ)數(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ù)對(duì)象的生成、類型轉(zhuǎn)化等等
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
// 2. 存儲(chǔ)處理的結(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ù)對(duì)象
private final ObjectWrapper objectWrapper;
private final ObjectFactory objectFactory;
private final ObjectWrapperFactory objectWrapperFactory;
private final ReflectorFactory reflectorFactory;
}
3.1 將數(shù)據(jù)庫「一行」數(shù)據(jù)轉(zhuǎn)化為對(duì)象,并逐字段解析成對(duì)應(yīng)的數(shù)據(jù)類型
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 1. 創(chuàng)建結(jié)果對(duì)象map - 數(shù)據(jù)實(shí)例化BaseTypeHandler#getNullableResult
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != nmonomialull && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
// 2. 創(chuàng)建MetaObject對(duì)象,「并將成員變量originalObject指向rowValue」,之后在操作直接對(duì)metaObject進(jìn)行操作; MetaObject --> 3.0.2
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
// 3. 是否應(yīng)用自動(dòng)映射,也就是通過resultType進(jìn)行映射
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ù)類型自動(dòng)映射
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反射生成他們?cè)贘ava中的類型
- 將反射生成的對(duì)象放入
metaObject.originalObject中
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
// 包含每一列數(shù)據(jù)類型對(duì)應(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實(shí)際為MySQL對(duì)應(yīng)的類型
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
// 將反射生成的對(duì)象放入metaObject.originalObject中
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
5、為每個(gè)字段匹配轉(zhuǎn)換Handler
5.1 得到每個(gè)字段的對(duì)應(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. 對(duì)于每一列 分別去找他們的 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) {
// 獲得列名對(duì)應(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ù)庫的實(shí)際類型在Java中對(duì)應(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
一個(gè)propertyType會(huì)對(duì)應(yīng)多個(gè)jdbcType的Handler
所以存儲(chǔ)Handler的變量類型是 Map<Type, Map<JdbcType, TypeHandler<?>>>
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
if (ParamMap.class.equals(type)) {
return null;
}
/**
* 一個(gè)propertyType會(huì)對(duì)應(yīng)多個(gè)jdbcType的Handler
* 所以存儲(chǔ)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) {
// 如果沒找到走默認(rèn)的,UnknownTypeHandler
handler = jdbcHandlerMap.get(null);
}
if (handler == null) {
// #591
handler = pickSoleHandler(jdbcHandlerMap);
}
}
// type drives generics here
return (TypeHandler<T>) handler;
}
7.2 給定Java中的類型,查找對(duì)應(yīng)的Handler
// 給定Java中的類型,查找對(duì)應(yīng)的Handler
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(type); // typeHandlerMap在初始化時(shí),JDBC Handler被手動(dòng)注冊(cè)上的 --> 8.1
//...
return jdbcHandlerMap;
}
8、Handler的初始化
8.1 org.apache.ibatis.type.TypeHandlerRegistry
TypeHandlerRegistry 的構(gòu)造函數(shù)會(huì)把所有的類型和他對(duì)應(yīng)的Handler都注冊(cè)到Map上
public final class TypeHandlerRegistry {
// 儲(chǔ)存所有的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ī)的映射情況都注冊(cè)上了
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);
}
}
五、具體實(shí)現(xiàn)
實(shí)現(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é)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
在Springboot中Mybatis與Mybatis-plus的區(qū)別詳解
MyBatis是一個(gè)優(yōu)秀的持久層框架,它對(duì)JDBC的操作數(shù)據(jù)庫的過程進(jìn)行封裝,MyBatisPlus (簡稱 MP)是一個(gè) MyBatis的增強(qiáng)工具,在 MyBatis 的基礎(chǔ)上只做增強(qiáng)不做改變,為簡化開發(fā)、提高效率而生,本文將給大家介紹了在Springboot中Mybatis與Mybatis-plus的區(qū)別2023-12-12
Java Web十條開發(fā)實(shí)用小知識(shí)
這篇文章主要介紹了Java Web十條開發(fā)實(shí)用小知識(shí)的相關(guān)資料,需要的朋友可以參考下2016-05-05
springmvc中進(jìn)行數(shù)據(jù)保存以及日期參數(shù)的保存過程解析
這篇文章主要介紹了springmvc中進(jìn)行數(shù)據(jù)保存以及日期參數(shù)的保存過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09
23種設(shè)計(jì)模式(12)java模版方法模式
這篇文章主要為大家詳細(xì)介紹了23種設(shè)計(jì)模式之java模版方法模式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11

