MyBatis源码学习—MyBatis参数处理器ParameterHandler详解

MyBatis的四大组件我们已经了解过两种了:一个是Executor,它在创建SqlSession的时候会被初始化,它是MyBatis 解析SQL请求首先会经过的第一道关卡,它的主要作用在于创建缓存,管理 StatementHandler 的调用,为 StatementHandler 提供 Configuration 环境等。StatementHandler 组件最主要的作用在于创建 Statement 对象与数据库进行交流,还会使用 ParameterHandler 进行参数配置,使用 ResultSetHandler 把查询结果与实体类进行绑定。这篇文章我们就来学习一下ParameterHandler。

1、ParameterHandler 简介

ParameterHandler相比于其他的组件就简单很多了,ParameterHandler>ParameterHandler相比于其他的组件就简单很多了,ParameterHandler 译为参数处理器,负责为 PreparedStatement 的 sql 语句参数动态赋值,这个接口很简单只有两个方法

ParameterHandler只有一个实现类DefaultParameterHandler,它实现了这两个方法:

  • getParamenterObject():用于读取参数
  • setParamenters():用于对 PreparedStatement 参数赋值

2、ParameterHandler对象的创建

参数处理器对象是在创建 StatementHandler 对象的同时被创建的,由 Configuration 对象负责创建,以BaseStatementHandler为例

在创建 ParameterHandler 时,需要传入mappedStatement 对象,用于读取参数和SQL语句

注意:一个 BoundSql 对象,就代表了一次sql语句的实际执行,而 SqlSource 对象的责任,就是根据传入的参数对象,动态计算这个 BoundSql, 也就是 Mapper 文件中节点的计算,是由 SqlSource 完成的,SqlSource 最常用的实现类是 DynamicSqlSource

Configuration.java

上面是 Configuration 创建 ParameterHandler 的过程,它实际上是交由 LanguageDriver来创建具体的参数处理器,LanguageDriver 默认的实现类是 XMLLanguageDriver,由它调用 DefaultParameterHandler 中的构造方法完成 ParameterHandler 的创建工作

//LanguageDriver中创建ParameterHanlder
public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        //直接new了一个DefaultParameterHandler
        return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
    }

3、ParameterHandler 解析绑定参数的流程

上面我们了解了参数处理器的创建过程,创建完成之后,该进行具体的解析工作,那么 ParameterHandler 如何解析SQL中的参数呢?SQL中的参数从哪里来的?

在PraparedStatementHandler的parameterize方法调用参数解析器的setParameters给PreparedStatement的SQL语句设置参数

DefaultParameterHandler.java

public void setParameters(PreparedStatement ps) {
  ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
  // parameterMappings 就是对 #{} 或者 ${} 里面参数的封装
  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  if (parameterMappings != null) {
    // 如果是参数化的SQL,便需要循环取出并设置参数的值
    for (int i = 0; i < parameterMappings.size(); i++) {
      ParameterMapping parameterMapping = parameterMappings.get(i);
      // 如果参数类型不是 OUT ,这个类型与 CallableStatementHandler 有关
      // 因为存储过程不存在输出参数,所以参数不是输出参数的时候,就需要设置。
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        // 得到#{}  中的属性名,比如:username
        String propertyName = parameterMapping.getProperty();
        // 如果 propertyName 是 Map 中的key
        if (boundSql.hasAdditionalParameter(propertyName)) { 
          // 通过key 来得到 additionalParameter 中的value值
          value = boundSql.getAdditionalParameter(propertyName);
        }
        // 如果不是 additionalParameters 中的key,而且传入参数是 null, 则value 就是null
        else if (parameterObject == null) {
          value = null;
        }
        // 如果 typeHandlerRegistry 中已经注册了这个参数的 Class对象,即它是Primitive 或者是String 的话
        else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          // 这一步的工作就是从当前实际传入的参数中获取到指定key('phone')的value值,比如是'15800000000'
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        // 获取该参数对应的typeHandler
        TypeHandler typeHandler = parameterMapping.getTypeHandler();
        // 获取该参数对应的jdbcType
        JdbcType jdbcType = parameterMapping.getJdbcType();
        if (value == null && jdbcType == null) {
          jdbcType = configuration.getJdbcTypeForNull();
        }
        try {
          //调用每个参数对应的typeHandler的setParameter方法为该ps设置正确的参数值  
          typeHandler.setParameter(ps, i + 1, value, jdbcType);
        } catch (TypeException e) {
          throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
        } catch (SQLException e) {
          throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
        }
      }
    }
  }
}

至此我们可以知道,每一次的调用查询(数据库查询,不走Mybatis缓存),Executor在执行doQuery的时候都会创建一个StatementHandler实例,每个StatementHandler在实例化的时候,都会创建并持有两个处理器即ParameterHandler和ResultSetHandler

这样StatementHandler就可以利用ParameterHandler完成预处理语句的参数化设置,以及结果查询出来以后再利用ResultSetHandler处理结果集

下图展示的就是一条SQL语句从Executor到ParameterHandler的关键流程:

留言区

还能输入500个字符