深入理解MyBatis-Statement执行体系

深入理解MyBatis - Statement执行体系


前言

StatementHandler负责处理Mybatis与JDBC之间Statement的交互。在这个章节中,还会涉及到ParameterHandlerResultSetHandler两个重要对象。StatementHandler可以单独抽离出来讲解,但是离开了ParamterHandler和ResultSetHandler,StatementHandler过于不完整,所以决定将ParameterHandler放在本章节中一起分析,至于ResultSetHandler涉及到的东西较多,且比较复杂,留着下个章节细讲。

1、StatementHandler

1、构成

​ StatementHandler的作用是处理MyBatis与Statement的交互接口。先看看StatementHandler接口的构成。

public interface StatementHandler {
  /**
   * 编译Statement
   * @param connection
   * @return
   * @throws SQLException
   */
  Statement prepare(Connection connection) throws SQLException;
  /**
   * 填充参数
   * @param statement
   * @throws SQLException
   */
  void parameterize(Statement statement) throws SQLException;
  /**
   * 批处理Statement
   * @param statement
   * @throws SQLException
   */
  void batch(Statement statement) throws SQLException;
  /**
   * 更新
   * @param statement
   * @return
   * @throws SQLException
   */
  int update(Statement statement)
      throws SQLException;
  /**
   * select
   * @param statement
   * @param resultHandler 结果处理器
   * @param <E>
   * @return
   * @throws SQLException
   */
  <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
  /**
   * 获取绑定的SQL
   * @return
   */
  BoundSql getBoundSql();
  /**
   * 获取参数处理器
   * @return
   */
  ParameterHandler getParameterHandler();
}
  • prepare:用于创建一个具体的 Statement 对象的实现类或者是 Statement 对象
  • parametersize:用于初始化 Statement 对象以及对sql的占位符进行赋值
  • batch:用于将Statement添加到批处理
  • update:用于通知 Statement 对象将 insert、update、delete 操作推送到数据库
  • query:用于通知 Statement 对象将 select 操作推送数据库并返回对应的查询结果

2、继承结构

在这里插入图片描述

​ 看到StatementHandler的继承图时,是不是觉得很熟悉?是的,Statement的继承关系几乎与Executor的继承关系如出一辙。我们来看看StatementHandler的实现类分别有什么作用。

  • BaseStatementHandler:是StatementHandler接口的基础实现类,是一个抽象类,与BaseExecutor一样,实现了子类的共性方法。
  • SimpleStatementHandler:是BaseStatementHandler的子类,用于处理简单的Sql处理,即没有参数的SQL,其中的parameterize方法为空实现
  • PreparedStatementHandler:用于处理带有参数的SQL,可以对应PreparedStatement
  • CallableStatementHandler:用于处理存储过程,对应CallableStatement
  • RoutingStatementHandler:Statement实现类的路由代理类

3、 PreparedStatementHandler

​ 在StatementHandler的各个子类中,我们实际上使用的最多的就是PreparedStatementHandler。其余像SimpleStatementHandler逻辑基本与PreparedStatementHandler一致,只是少了参数填充这个过程,所以不对其进行分析。我们主要来看看PreparedStatementHandler中实现了哪些逻辑。

public class PreparedStatementHandler extends BaseStatementHandler {

  public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
  }

  @Override
  public int update(Statement statement) throws SQLException {
    // 调用PreparedStatement.execute和PreparedStatement.getUpdateCount
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    int rows = ps.getUpdateCount();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }

  @Override
  public void batch(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.addBatch();
  }

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }

  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    // 调用Connection.prepareStatement
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.prepareStatement(sql);
    }
  }

  @Override
  public void parameterize(Statement statement) throws SQLException {
    // 调用ParameterHandler.setParameters
    parameterHandler.setParameters((PreparedStatement) statement);
  }

}

​ 我们用过走读PreparedStatementHandler的源码后发现,其实这一部分的源码非常简单。是对Statement接口的调用进行简单的封装。在update方法中存在一个KeyGenerator对象,该对象是在进行insert操作后,获取插入的记录的ID,底层也是调用的Statement接口的getGeneratedKeys方法。至于调用的是具体是哪个KeyGenerator,逻辑在MappedStatement的builder方法中,感兴趣的同学自行去翻看。

​ 除此之外,在上方的代码中,还有两个非常重要的方法,分别是queryparameterize。query方法在调用完execute方法后,调用了ResultSetHandler的handleResultSet方法对查询回来的ResultSet中数据进行处理。而parameterize的实现,则是交由ParameterHandler的setParameters来完成。接下来我们一起先来看看ParameterHandler是如何解析填充动态Sql的参数的。

2、ParameterHandler

​ ParameterHandler接口比较简单,只有getParameterObjectsetParameters两个方法,前者用于获取参数,后者用于设置参数。这个接口也只有一个默认实现类DefaultParameterHandler。

public class DefaultParameterHandler implements ParameterHandler {

  @Override
  public Object getParameterObject() {
    return parameterObject;
  }

  /**
   * 设置参数
   * @param ps
   * @throws SQLException
   */
  @Override
  public void setParameters(PreparedStatement ps) throws SQLException {
    // 获取参数映射列表
    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) {
          // 如果不是OUT,才设进去
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) {
            // 若有额外的参数, 设为额外的参数
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            // 若参数为null,直接设null
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            // 若参数有相应的TypeHandler,直接设object
            value = parameterObject;
          } else {
            // 除此以外,MetaObject.getValue反射取得值设进去
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            // 不同类型的set方法不同,所以委派给子类的setParameter方法
            jdbcType = configuration.getJdbcTypeForNull();
          }
          typeHandler.setParameter(ps, i + 1, value, jdbcType);
        }
      }
    }
  }
}

​ setParameters的方法逻辑其实比较简单,通过遍历方法参数映射列表,得到占位符 **?**的对应属性的值, 交由相应的TypeHandler去进行赋值。讲个简单的例子,假设有现在下方这个SQL映射。

<select id="findUserById" resultMap="result_user">
        select * from users where id = #{id, jdbcType=INTEGER}
</select>

​ 在MyBatis对xml映射文件进行解析时,会将上述的MappedStatement解析成下面这种格式,同时将参数

select * from users where id = ?

​ 然后在进行设置参数时,也就是执行上方的setParameters方法时,会根据parameterObject的类型来对参数进行赋值,按照我举的例子,id这个参数传递进来时,其实是对应的一个整型的数据类型,那么其实是会进入下方这个分支。MyBatis通过预先设置好数据类型映射来判断是否可以直接对该参数对象直接进行处理。如果可以的话,直接将value的值设置为传递进来的参数。

if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
	 value = parameterObject;
}

​ 如果是另外一种情况,该参数属于复杂类型的参数,比如我们的POJO类,一个对象中可能包含了十来个参数,甚至更多,这个时候,会进入最后一个判断分支。MyBatis会将该参数包装成一个MetaObject对象,根据propertyName,采用反射的形式来获取对应参数的值。最常见的场景就是insert的时候。

3、ResultSetHadler

​ 说完ParameterHandler后,我们接着来看ResultSetHandler。MyBatis在参数设置时,大多数情况下,我们都是在接口方法参数使用**@Param**注解来映射SQL中的占位符参数。但是结果集映射相比于参数映射的情况会复杂的多,MyBatis提供的写法也更加的丰富。所以在这种情况下, 结果集处理起来,难度肯定也要比参数处理更上一层。但同时也时因为有了ResultSetHandler,才将我们从复杂、繁琐的结果集处理中解放出来。

​ 在设计上ResultSetHandler和ParameterHandler一样,结构上比较的简单,接口只定义了两个方法,handleResultSet用来处理SQL的结果集,handleOutputParameters用于处理SP的出参。ResultSetHandler和ParameterHandler一样,只有一个默认实现类DefaultResultSetHandler。但是由于ResultSetHandler的实现与MyBatis庞大的映射体系息息相关,且相对来说较为复杂,所以在这个章节中,我们先简单的带过,只需要知道ResultSetHandler在StatementHandler执行流程中扮演了什么角色,至于ResultSetHandler是如何实现的我们留着下个章节再进行分析。

4、执行流程

​ 在看完StatementHandler的执行体系之后,我们结合Executor执行体系,结合起来看一下整体的执行流程。

  1. 以带参数的查询为例,在SimpleExecutor中,我们根据已知的MappedStatement、parameter(参数)、RowBounds及BoundSql,去创建一个StatementHandler(PreparedStatementHandler)对象
  2. 通过handler(指StatementHandler)的prepare去实例化一个Statement(PreparedStatement)对象
  3. 紧接着,调用handler的parameterize方法,在PreparedStatementHandler的parameterize方法中,实际上是交由DefaultParameterHandler的setParameters方法,给SQL设置参数
  4. 在设置完参数后,调用handler的query方法,去执行PreparedStatement。
  5. 执行PreparedStatement的execute后,使用resultSetHandler的handleResultSets方法,根据映射去转换查询得到的ResultSet。
@Override
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.<E>query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    //调用StatementHandler.prepare
    stmt = handler.prepare(connection);
    //调用StatementHandler.parameterize
    handler.parameterize(stmt);
    return stmt;
  }

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();
    if (boundSql == null) {
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }
    this.boundSql = boundSql;
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }

@Override
  public Statement prepare(Connection connection) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);
      setStatementTimeout(statement);
      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);
    }
  }

@Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }

@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }

​ 上述流程的关键代码都在基本上都在上面的代码块中。下方再一张图作为总结。

在这里插入图片描述

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值