一、前言

ParameterHandler 是 Mybatis 四大处理器之一,负责为 PreparedStatement 的 SQL 语句参数动态赋值。

二、结构体系

2.1 接口

  1. // org.apache.ibatis.executor.parameter.ParameterHandler
  2. /**
  3. * 参数处理器,对 {@code PreparedStatement} 设置参数
  4. */
  5. public interface ParameterHandler {
  6. /**
  7. * 获取参数对象
  8. * @return
  9. */
  10. Object getParameterObject();
  11. /**
  12. * 对 {@link PreparedStatement} 的参数赋值
  13. * @param ps
  14. * @throws SQLException
  15. */
  16. void setParameters(PreparedStatement ps) throws SQLException;
  17. }

ParameterHandler 定义了两个简单的接口,一个是获取参数对象,一个是对 PreparedStatement 进行赋值操作。

2.2 继承

ParameterHandler.png
Mybatis ParameterHandler 默认只有一个实现类 DefaultParamerter ,这个类最重要的方法是完成对 PreparedStatement 对象的赋值操作。

2.3 DefaultParamerter#setparameters

下图是 DefaultParamerter#setparameters 源码,从 BoundSql 对象中获取所有的参数(每个参数都被封装为 ParameterMapping 对象),循环遍历参数集合,完成参数写入。循环遍历过程如下:

  1. 确定参数对象。
  2. ParameterMapping 获取类型处理器
  3. 由对应的类型处理器给 PreparedStatement 对象赋值

DefaultParameterHandler#setparameters.png

三、生命周期

我们先回顾一下 Mybatis 加载流程:

  1. 解析配置文件(比如 mybatis-config.xml),并使用 Configuration 配置
    1. 解析全局配置,比如缓存、类型处理器
    2. 解析 Mapper 映射文件,重要的对象有
      1. BoundSql: 包含 SQL、参数数据
      2. MappedStatement: 每个<select|inert|update|delete> 标签都对应一个 MappedStatement ,包含所有的标签配置属性。
  2. 解析配置文件之后,根据配置文件获取 SqlSessionFactory 工厂
  3. 根据工厂获取 SqlSession 对象,这个对象就是 Mybatis 暴露给用户的接口,利用这个对象就可以进行 SQL 操作。
  4. 假设我们使用 session.selectOne(statementId) 这最简单的查询语句,捋一下 Mybatis 处理流程。
    1. 首先,我们需要根据 statementId获取 MappedStatement ,这个对象非常重要,里面包含了待执行 SQL 重要信息。
    2. 接着,使用 Executor 执行器进行 query() 方法调用。(Executor初始化请看)。
    3. 轮到 Executor 执行器,它会判断是当前查询是否已经缓存,如果不存在,则查询数据库,对应方法 (org.apache.ibatis.executor.BaseExecutor#queryFromDatabase())。
    4. Executor 并非真正封装 JDBC 调用,而是 StatementHandler 详见。先根据配置文件获取 StatementHandler (一般为 PreparedStatementHandler)对象,它封装了底层 JDBC 相应的调用方法。
    5. 在调用 execute() 等方法之前,我们需要对 SQL 进行预编译操作,由上述的 StatementHandler完成。
    6. 紧接着,对预编译对象 PreparedStatement 对象进行赋值,而这个赋值操作就是今天讲到的 ParameterHandler
    7. 再进行 execute() 等方法调用,结果存储在 PreparedStatement
    8. 这里,轮到结果处理器 ResuleSetHandler 上场,是它将我们的结果集转换为想要的对象/集合。
    9. 返回并关闭对应资源 。
    10. 当然,上述有一些细节并未提及,比如 ID 生成器等,但是这不妨碍你理解整个流程。

从上面看到,类型的解析在步骤f,我们已经可以根据配置类获取足够多的关于类型相关的信息,因此,根据类型获取对应的类型处理器就可以完成类型的转换操作。

3.1 类型处理器在什么时候被确定?

从 图 2.3 源码可以看到,TypeHandler 是直接从 ParameterMapping 对象中直接获取,所以可以追踪 ParameterMapping 在何时创建的就可以找到参数类型处理器何时确定。

3.1.1 ParameterMapping

ParameterMapping 是Mybatis 参数映射类,它负责存储参数已解析的 XML SQL 参数。下图是它定义的成员变量:
ParameterMapping.png

3.1.2 确定 TypeHandler

(1)显示配置

若标签上有 parameterType 配置,如:
Select_parametertype.png
则在 XML 解析时就能直接确定对应的类型处理器:
parameterhandler_with_xml.png

(2)隐式配置

当用户没有显示设置参数类型时,则 javaType 为 Object 类型:
parameterhandler_without_setting.png
别担心,Mybatis 会在赋值时会进一步解析所对应的类型。从上面可以看到,此时 TypeHandlerUnknownTypeHandler ,所以我们看一看它到底如何获取真正的类型处理器。
UnknownTypeHandler#setnonnullparameter.png
UnknownTypeHandler_resolveTypeHandler.png
以上是当 Mybatis 解析配置文件时无法确定类型时才会用到的 UnknownTypeHandler ,那为什么一开始确定不了呢? 因为我们无法在一开始就能获取到参数对象,所以如果使用 getClass() 获取对应数据类型信息,也就无法确定参数类型处理器。

3.2 参数在什么时候被写入

参数写入,意味着我们已经准备好 SQL 语句 和 参数。

3.2.1 何时准备 SQL 语句呢?

当我们调用 SqlSession API 进行 SQL 语句查询,通过 MappedStatement#getBoundSql(parameter) 就能获取可以预编译的 SQL 详情。
MappedStatement_getBoundSql.png
而他也是代理给 SqlSource#getBoundSql() 方法完成。而在 SqlSource继承体系 提到过 SqlSource ,根据不同的类型拥有不同的解析策略。

3.2.2 何时设置参数呢?

既然 SQL 已经准备好,其实参数也是上一步也准备好了。封装在 BoundSqlList<ParameterMapping> 参数中。如上图所示。
那在哪里进行参数的设置呢? 其实就是由 ParameterHandler 来完成的。看一下调用栈:
ParameterHandler_stack.png
相关源码

五、总结

  1. ParameterHandler 只有一个实现类,而它完成对预编译的 SQL 语句参数赋值操作。