java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Mybatis返回类型为Map时遇到的类型转化的异常

Mybatis返回类型为Map时遇到的类型转化的异常问题

作者:猩猩丶灯

这篇文章主要介绍了Mybatis返回类型为Map时遇到的类型转化的异常问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

一、问题背景

为了满足通用化的设计,项目中通过入参来指定查询的数据表并以Map的形式获取所有数据,同时希望所有取得的数据都是String的格式,因此使用List<Map<String, String>>来存储取得的数据。

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<Map<String, Object>> selectAllByStatement(...)

每次使用的时候,可以toString()转化再使用

方法二

本文主要讨论这种方式

mybatis从数据库中读取数据,并以反射的方式生成Object的对象

然后使用对应类型的TypeHandler对其进行数据类型的转化

因此,我们需要在TypeHandler上做手脚,自定义TypeHandler并将其注册到TypeHandlerMap中,让特定的数据类型转化走我们的TypeHandler

在此次问题中,我们以gmv字段为例,

首先逻辑走到 6.1的24行 handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);

propertyTypeObjectjdbcTypeDouble.class

但是mybatis没有定义ObjectDouble的handler

所以会走到 6.1的32行 handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);

javaType是从DoublejdbcTypeDouble.class

然后会匹配到org.apache.ibatis.type.DoubleTypeHandler,把数据转换成Double

所以,我们该怎么做呢,我们可以定义一个ObjectDouble的Handler,让他在6.1的24行的时候匹配到,里面转化的逻辑我们定义成Object.toString(),具体操作看《五、具体实现》

四、mybatis处理查询结果的源码

本节是对mybatis查询数据时,对查询结果的处理过程的个人理解

1、查询

  @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、准备处理查询结果

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);
  }
}

3、每一行的处理

解析数据库中取得的数据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、数据类型自动映射

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 为每一列字段找到他的转换Handler

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、为每个字段匹配转换Handler

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、字段匹配Handler

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<String, Map<Class<?>, 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、给定Type和jdbcType匹配Handler

7.1 org.apache.ibatis.type.TypeHandlerRegistry#getTypeHandler

根据type和jdbcType一起查符合的Handler

一个propertyType会对应多个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会对应多个jdbcType的Handler
   *  所以存储Handler的变量类型是 Map<Type, Map<JdbcType, TypeHandler<?>>>  
   **/
  // 先根据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、Handler的初始化

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<Type, Map<JdbcType, TypeHandler<?>>>
        }
        map.put(jdbcType, handler);
      }
      allTypeHandlersMap.put(handler.getClass(), handler);
  }
}

五、具体实现

实现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);
    }
}

配置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();
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文