为了满足通用化的设计,项目中通过入参来指定查询的数据表并以Map
的形式获取所有数据,同时希望所有取得的数据都是String
的格式,因此使用List
来存储取得的数据。
Mapper.java
List<Map<String, String>> selectAllByStatement(...)
Mapper.xml
<select id="selectAllByStatement" parameterType="java.lang.String" resultType="java.util.Map">
...
select>
从数据库中取得的数据无法被正常使用↓
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
字段在数据库中的数据类型是double
结果导致maps.get(0)
中的数据,编译时看上去是Map定义的类型String
,运行时是数据库中对应的类型Double
,然后出现以上错误。具体错误原因没有细究
方法一、
Mapper中返回类型定义为 Object,即:
List
每次使用的时候,可以toString()
转化再使用
方法二、
本文主要讨论这种方式
∵
mybatis从数据库中读取数据,并以反射的方式生成Object的对象
然后使用对应类型的TypeHandler对其进行数据类型的转化
∴
因此,我们需要在TypeHandler上做手脚,自定义TypeHandler并将其注册到TypeHandlerMap中,让特定的数据类型转化走我们的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
,把数据转换成Double
所以,我们该怎么做呢,我们可以定义一个
Object
→Double
的Handler,让他在6.1的24行的时候匹配到,里面转化的逻辑我们定义成Object.toString()
,具体操作看《五、具体实现》
本节是对mybatis查询数据时,对查询结果的处理过程的个人理解
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行查询
ps.execute();
// 处理查询结果 --> 2.1
return resultSetHandler.handleResultSets(ps);
}
2.0.1
public class ResultSetWrapper {
private final ResultSet resultSet; // 保存着原始数据
private final TypeHandlerRegistry typeHandlerRegistry;
private final List<String> columnNames = new ArrayList<>(); // 数据库列名
private final List<String> classNames = new ArrayList<>(); // 对应的Java类型
private final List<JdbcType> jdbcTypes = new ArrayList<>(); // 对应的数据库类型
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
逐行处理并存储数据
// rsw 保存着查询结果的各种相关数据 --> 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. 处理:数据对象的生成、类型转化等等
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
// 2. 存储处理的结果
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
解析数据库中取得的数据rsw
,反射生成Java的类型
3.0.1
public class MetaObject {
private final Object originalObject; // 指向数据对象
private final ObjectWrapper objectWrapper;
private final ObjectFactory objectFactory;
private final ObjectWrapperFactory objectWrapperFactory;
private final ReflectorFactory reflectorFactory;
}
3.1
将数据库「一行」数据转化为对象,并逐字段解析成对应的数据类型
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 1. 创建结果对象map - 数据实例化BaseTypeHandler#getNullableResult
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != nmonomialull && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
// 2. 创建MetaObject对象,「并将成员变量originalObject指向rowValue」,之后在操作直接对metaObject进行操作; MetaObject --> 3.0.2
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
// 3. 是否应用自动映射,也就是通过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.0.1
private static class UnMappedColumnAutoMapping {
private final String column; // 列名
private final String property; // 属性名
private final TypeHandler<?> typeHandler; // 该列数据的数据类型转化Handler
private final boolean primitive;
}
4.1
metaObject.originalObject
中private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
// 包含每一列数据类型对应的转化Handler --> 5.1
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (!autoMapping.isEmpty()) {
for (UnMappedColumnAutoMapping mapping : autoMapping) {
// 数据转换;表面为Object实际为MySQL对应的类型
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.1
得到每个字段的对应的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。 // 优先用属性类型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.1
优先用属性类型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, TypeHandler>>>
if (columnHandlers == null) {
columnHandlers = new HashMap<>();
typeHandlerMap.put(columnName, columnHandlers);
} else {
handler = columnHandlers.get(propertyType);
}
// 没有缓存的话↓
if (handler == null) {
// 获得列名对应的数据库中的类型JdbcType
JdbcType jdbcType = getJdbcType(columnName); // 数据库中的类型
// ** 用属性类型propertyType去找 ** --> 7.1
handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
// ↑ 用属性类型propertyType没找到的话 就 ↓
if (handler == null || handler instanceof UnknownTypeHandler) {
// 找该列在数据库的实际类型在Java中对应的javaType
final int index = columnNames.indexOf(columnName);
final Class<?> javaType = resolveClass(classNames.get(index));
if (javaType != null && jdbcType != null) {
// ** 用数据库类型去找 ** --> 7.1
handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
}
// ...
}
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = new ObjectTypeHandler();
}
// 缓存记录
columnHandlers.put(propertyType, handler);
}
return handler;
}
}
7.1
org.apache.ibatis.type.TypeHandlerRegistry#getTypeHandler
根据type和jdbcType一起查符合的Handler
一个propertyType会对应多个jdbcType的Handler
所以存储Handler的变量类型是 Map
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
if (ParamMap.class.equals(type)) {
return null;
}
/**
* 一个propertyType会对应多个jdbcType的Handler
* 所以存储Handler的变量类型是 Map>>
**/
// 先根据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中的类型,查找对应的Handler
// 给定Java中的类型,查找对应的Handler
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(type); // typeHandlerMap在初始化时,JDBC Handler被手动注册上的 --> 8.1
//...
return jdbcHandlerMap;
}
8.1
org.apache.ibatis.type.TypeHandlerRegistry
TypeHandlerRegistry
的构造函数会把所有的类型和他对应的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());
//... 把所有常规的映射情况都注册上了
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>>
}
map.put(jdbcType, handler);
}
allTypeHandlersMap.put(handler.getClass(), handler);
}
}
自定义TypeHanlder需要继承BaseTypeHandler
用@MappedJdbcTypes
,@MappedTypes
来定义此Handler应用于哪些类型的转换
@MappedTypes
指定字段在Java的数据类型
@MappedJdbcTypes
指定字段在数据库中的类型
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);
}
}
在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();
}