Mybatis 源码 ④ :TypeHandler

文章目录

  • 一、前言
  • 二、DefaultParameterHandler
    • 1. DefaultParameterHandler#setParameters
      • 1.1 UnknownTypeHandler
      • 1.2 自定义 TypeHandler
  • 三、DefaultResultSetHandler
    • 1. hasNestedResultMaps
    • 2. handleRowValuesForNestedResultMap
      • 2.1 resolveDiscriminatedResultMap
      • 2.2 createRowKey
      • 2.3 getRowValue
        • 2.2.1 createResultObject
        • 2.2.2 applyAutomaticMappings
        • 2.2.3 applyPropertyMappings
        • 2.2.4 applyNestedResultMappings
      • 2.4 storeObject
    • 3. handleRowValuesForSimpleResultMap

一、前言

Mybatis 官网 以及 本系列文章地址:

  1. Mybatis 源码 ① :开篇
  2. Mybatis 源码 ② :流程分析
  3. Mybatis 源码 ③ :SqlSession
  4. Mybatis 源码 ④ :TypeHandler
  5. Mybatis 源码 ∞ :杂七杂八

书接上文 Mybatis 源码 ③ :SqlSession
。我们这里来看下 DefaultParameterHandler 和 DefaultResultSetHandler 的处理过程。

二、DefaultParameterHandler

DefaultParameterHandler 类图如下,可以看到其实现了 ParameterHandler 接口,我们可以通过 Plugin 的方式对 ParameterHandler 进行增强。这里我们主要来看 DefaultParameterHandler 的具体作用。
Mybatis 源码 ④ :TypeHandler_第1张图片

1. DefaultParameterHandler#setParameters

在 SimpleExecutor 和 BaseExecutor doUpdate、doQuery、doQueryCursor 等方法中会调用 prepareStatement 方法,在其中会调用 StatementHandler#parameterize 来对参数做预处理,里面会调用 PreparedStatementHandler#parameterize,该方法如下:

  @Override
  public void parameterize(Statement statement) throws SQLException {
    // 这里会调用 DefaultParameterHandler#setParameters
    parameterHandler.setParameters((PreparedStatement) statement);
  }

因此我们可以知道,在Sql 执行前,会调用 DefaultParameterHandler#setParameters 方法来对参数做处理,这也就给了 TypeHandler 的参数转换提供了条件。


DefaultParameterHandler#setParameters 实现如下:

  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 获取当前Sql执行时的参数
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          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);
          }
          // 如果当前参数指定了类型转换器, 则通过类型转换器进行转换。否则交由 UnknownTypeHandler 
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
          	// 调用类型转换器进行处理, 默认情况下是 UnknownTypeHandler 
          	// jdbcType 是我们通过 jdbcType 属性指定的类型,没有指定则为空 
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

上面可以看到逻辑比较简单:遍历所有参数,并且参数值交由 typeHandler.setParameter 来处理。需要注意的是这里的 typeHandler 如果没有指定默认是 UnknownTypeHandler。在UnknownTypeHandler 中则会根据参数实际类型来从注册的 TypeHandler 中选择合适的处理器来处理。下面我们具体来看。

1.1 UnknownTypeHandler

UnknownTypeHandler#setParameter 会调用 UnknownTypeHandler#setNonNullParameter, 我们以该方法为例,UnknownTypeHandler 的其他方法也类似。

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
      throws SQLException {
    // 根据参数类型来获取 类型处理器
    // jdbcType 是我们通过 jdbcType 属性指定的类型,没有指定则为空 
    TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
    // 调用类型处理器处理
    handler.setParameter(ps, i, parameter, jdbcType);
  }

  private TypeHandler<?> resolveTypeHandler(Object parameter, JdbcType jdbcType) {
    TypeHandler<?> handler;
    // 参数为空直接返回 ObjectTypeHandler
    if (parameter == null) {
      handler = OBJECT_TYPE_HANDLER;
    } else {
      // 从注册的 TypeHandler 中根据类型选择合适的处理器
      handler = typeHandlerRegistrySupplier.get().getTypeHandler(parameter.getClass(), jdbcType);
      // check if handler is null (issue #270)
      // 如果没找到返回 ObjectTypeHandler
      if (handler == null || handler instanceof UnknownTypeHandler) {
        handler = OBJECT_TYPE_HANDLER;
      }
    }
    return handler;
  }

这里可以看到, 在执行Sql前会通过 DefaultParameterHandler#setParameters 对参数做一次处理。

  1. 如果参数指定了 typeHandler 则使用参数指定的 TypeHandler
  2. 如果参数没有指定,则使用 UnknownTypeHandler 来处理。而 UnknownTypeHandler 会根据参数的实际类型和 jdbcType 来从已注册的 TypeHandler 选择合适的处理器对参数做处理。

1.2 自定义 TypeHandler

我们可以自定义 TypeHandler 来实现指定字段的特殊处理,如用户密码在数据库中不能明文展示,而在代码中我们明文处理,则就可以通过如下方式定义:

  1. 创建一个 PwdTypeHandler 类,继承 BaseTypeHandler
public class PwdTypeHandler extends BaseTypeHandler<String> {
	// 定义加解密方式
    private static final SymmetricCrypto AES = new SymmetricCrypto(
            SymmetricAlgorithm.AES, "1234567890123456".getBytes());

	// 赋值时加密
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, AES.encryptBase64(parameter));
    }
	// 取值时解密
    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return AES.decryptStr(rs.getString(columnName));
    }

    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return AES.decryptStr(rs.getString(columnIndex));
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return AES.decryptStr(cs.getString(columnIndex));
    }
}
  1. XML 指定使用的 typeHandler,如下
    <resultMap id="BaseResultMap" type="com.kingfish.entity.SysUser">
        
        <result property="id" column="id" jdbcType="INTEGER"/>
        <result property="userName" column="user_name" jdbcType="VARCHAR"/>
        <result property="password" column="password" jdbcType="VARCHAR" typeHandler="com.kingfish.config.handler.PwdTypeHandler"/>
        
    resultMap>
  1. 在实际调用接口时新增或返回时都会使用 PwdTypeHandler 来对指定字段做处理人,如下:
    1. 调用接口明文新增时入库是加密后结果 Mybatis 源码 ④ :TypeHandler_第2张图片
    2. 数据库加密,查询返回是明文
      Mybatis 源码 ④ :TypeHandler_第3张图片

三、DefaultResultSetHandler

DefaultResultSetHandler实现了ResultSetHandler 接口,ResultSetHandler 见名知意,即为结果集合处理器。所以下面我们来看看该方法的具体逻辑 :

  @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    // 获取第一个结果集  ResultSet 并包装成 ResultSetWrapper 
    ResultSetWrapper rsw = getFirstResultSet(stmt);
	
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    // ResultMap 的数量, 当使用存储过程时,可能会有多个,我们这里不考虑存储过程的多个场景。
    int resultMapCount = resultMaps.size();
    // ResultMap 数量校验 :rsw != null && resultMapCount < 1
    validateResultMapsCount(rsw, resultMapCount);/**********************************************************************/
    // 1.对 ResultMap 的处理
    // 循环所有的 ResultMap
    while (rsw != null && resultMapCount > resultSetCount) {
     // 获取当前 ResultMap
      ResultMap resultMap = resultMaps.get(resultSetCount);
      // 1.1 根据ResultMap中定义的映射规则处理ResultSet,并将映射得到的Java对象添加到 multipleResults集合中保存
      handleResultSet(rsw, resultMap, multipleResults, null);
      // 1.2 获取下一个 ResultSet 
      rsw = getNextResultSet(stmt);
      // 1.3 清理nestedResultObjects集合,这个集合是用来存储中间数据的
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
	/**********************************************************************/
    // 2. 对 ResultSets 的处理
	// 对 resultSet 处理,