MyBatis StatementHandler

MyBatis StatementHandler

紧接上一篇,现在就来看看 StatementHandler 是如何从 Executor 手中接棒并完成后续操作的吧。

StatementHandler 分类

mybatis-statementHandler
可以看到 StatementHandler 的类关系和 Executor 非常相似。
其中 BaseStatementHandler 的三个子类都是和 JDBC 中的 Statement 相对应的。

  • SimpleStatementHandler 对应 JDBC 中常用的 Statement 接口
  • PreparedStatementHandler 对应 JDBCPrepareStatement ,用于预编译的 SQL 接口
  • CallableStatementHandler 对应 JDBC 中的 CallableStatement,用于执行存储过程相关的接口
  • RoutingStatementHandler 是上面三个接口的路由,没有实际操作,只是负责它们的创建和调用

创建 StatementHandler

创建 StatementHandler 是在 Configuration 中完成的

  //org.apache.ibatis.session.Configuration

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    //创建 StatementHandler
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    //熟悉的身影,插件的支持
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

具体看一下 RoutingStatementHandler 的创建逻辑

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
  }

就是这么简单,根据 MappedStatementstatementType 来决定创建那种 StatementHandler,默认是 PREPARED。也可以通过 mapper.xml 文件对应的 statement 节点上指定 statementType,例如指定创建 SimpleStatementHandler

<select id="findLastPage" resultMap="userResult" statementType="STATEMENT">
    SELECT * FROM user ORDER BY `update_time` DESC LIMIT 10
</select>

创建 Statement

通过上面创建的 StatementHandler 来创建 JDBCStatement

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    //①预备 Statement
    stmt = handler.prepare(connection, transaction.getTimeout());
    //②对得到的 Statement 做参数化处理
    handler.parameterize(stmt);
    return stmt;
  }

预备 Statement

预备 Statement 调用的是 BaseStatementHandler#prepare方法

  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      //实例化 statement
      statement = instantiateStatement(connection);
      //设置超时时间
      setStatementTimeout(statement, transactionTimeout);
      //设置每次查询的数量
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

其中实例化操作 instantiateStatement 是一个抽象方法,需要子类去实现,这样就实现了不同的 StatementHandler 产生不一样的 Statement 的效果。实际上就是一开始说的 BaseStatementHandler 三个子类与 JDBCStatement 的对应关系。

Statement 参数化处理

Statement 参数化处理是通过 BaseStatementHandler#parameterize 方法来完成的,一起来看看三个子类是如何处理的。

  //SimpleStatementHandler 对应的是一般的 Statement,不需要参数化处理,所以是空实现
  public void parameterize(Statement statement) {
    // N/A
  }

  //PreparedStatementHandler 直接通过 ParameterHandler 完成参数化处理
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }

  //CallableStatementHandler 的参数化处理和 PreparedStatementHandler 一样依赖于 ParameterHandler
  public void parameterize(Statement statement) throws SQLException {
    registerOutputParameters((CallableStatement) statement);
    parameterHandler.setParameters((CallableStatement) statement);
  }

这下我们清楚了,需要进行参数化处理的 Statement 都交给 ParameterHandler#setParameters 去完成了。ParameterHandler 只有一个默认实现 DefaultParameterHandler,具体的参数解析逻辑代码我就不贴了,感兴趣的自己去看看 ->_->

SQL 执行

获得 Statement 实例后,回到 SimpleExecutor#doQuery 方法中看看接下来是如何完成 SQL 的执行的吧。这里为了不用翻 Executor 的文章,就再贴一下代码。

  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      //看这里,最后一步
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

直接调用 StatementHandler#query 方法完成了查询操作,点进去看看有啥骚操作

  //org.apache.ibatis.executor.statement.SimpleStatementHandler
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    // JDBC
    statement.execute(sql);
    return resultSetHandler.handleResultSets(statement);
  }

  //org.apache.ibatis.executor.statement.PreparedStatementHandler
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // JDBC
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
  }

  //org.apache.ibatis.executor.statement.CallableStatementHandler
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    CallableStatement cs = (CallableStatement) statement;
    // JDBC
    cs.execute();
    List<E> resultList = resultSetHandler.handleResultSets(cs);
    resultSetHandler.handleOutputParameters(cs);
    return resultList;
  }

是不是😥了?就直接 JDBC 了,并且最后都会调用 ResultSetHandler#handleResultSets 方法完成结果集的解析,默认实现也就只有 DefaultResultSetHandler,感兴趣的自己看看吧。

总结

到这里处理缓存处理、插件支持没有介绍外,大体的流程也都串通了。

  1. 服务启动加载配置文件,创建 Configuration,再通过 SqlSessionFactoryBuilder 构建 SqlSessionFactory
  2. 调用 SqlSessionFactory#openSession 创建 SqlSession 实例,同时会创建一个 Executor
  3. SqlSession 的所有 CRUD 操作都会被委托给 Executorqueryupdate 方法
  4. 底层操作依赖于 JDBC,所以就得创建 Statement,而 StatementHandler 就是用来干这个的,在 ExecutorJDBC 之间搭桥
  5. JDBC 操作之前通过 ParameterHandler 完成请求参数的解析,在操作之后通过 ResultSetHandler 完成结果集的解析。

虽然举的例子是查询方法,但更新方法的整个流程也是一样的,感兴趣的自己断点调试调试吧😁