MyBatis原理系列(五)-手把手带你了解Statement、StatementHandler、MappedStatement间的关系

1,538

在上篇文章中我们介绍了Executor的三种执行器,并且在执行update和query方法的时候,都会有一个Statement和StatementHandler的操作。当时我们一笔带过了,在这篇文章中我们将介绍Statement,StatementHandler的关系,以及它们的实现细节。

系列文章

MyBatis原理系列(一)-手把手带你阅读MyBatis源码
MyBatis原理系列(二)-手把手带你了解MyBatis的启动流程
MyBatis原理系列(三)-手把手带你了解SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的关系
MyBatis原理系列(四)-手把手带你了解MyBatis的Executor执行器
MyBatis原理系列(五)-手把手带你了解Statement、StatementHandler、MappedStatement间的关系
MyBatis原理系列(六)-手把手带你了解BoundSql的创建过程
MyBatis原理系列(七)-手把手带你了解如何自定义插件
MyBatis原理系列(八)-手把手带你了解一级缓存和二级缓存
MyBatis原理系列(九)-手把手带你了解MyBatis事务管理机制

1. Statement 对象

我们先来了解下Statement对象,在原始JDBC操作中,会有加载驱动,设置属性,获取连接,创建Statement对象...等一系列操作。Statement对象在JDBC操作中就是向数据库发送sql语句,并获取到执行结果。Statement对象有三种,分别是StatementPreparedStatementCallableStatement。它们的继承关系如下

image.png

  1. Statement:可以发送字符串类型的sql,不支持传递参数。适用于静态sql语句。
  2. PreparedStatement: 预编译的sql语句,接受参数传入,并且可以防止sql注入,提高安全性。Sql语句会编译在数据库系统,适用于多次执行相同的sql,因此性能高于Statement。
  3. CallableStatement:在执行存储过程时使用,并且可以接受参数传入。

Statement 接口方法有这么多,主要就是执行更新,查询,获取结果等操作。

image.png

2. StatementHandler 对象

2.1 StatementHandler 对象初识

StatementHandler 对象从字面意义上来讲就是管理Statement对象的了。它有两个直接实现,一个是BaseStatementHandler,另一个是RoutingStatementHandler。然后BaseStatementHandler有三个实现分别是SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler,他们分别管理的就是上面讲到的Statement,PreparedStatement和CallableStatement对象了。继承关系如下图,是不是很像Executor的继承关系,BaseStatementHandler是使用了适配器模式,减少了实现接口的复杂性,RoutingStatementHandler则是包装了以上三种Handler,作为一个代理类进行操作。

image.png

StatementHandler 接口的方法如下

/**
 * @author Clinton Begin
 */
public interface StatementHandler {

  // 创建Statement对象
  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;

  // 对Sql中的占位符进行赋值
  void parameterize(Statement statement)
      throws SQLException;
  
  // 添加到批处理操作中
  void batch(Statement statement)
      throws SQLException;
  
  // 执行更新操作
  int update(Statement statement)
      throws SQLException;
  
  // 执行查询操作并且返回结果
  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;
  
  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;

  // 获取BoundSql对象
  BoundSql getBoundSql();

  // 获取参数处理器
  ParameterHandler getParameterHandler();

}
2.2 StatementHandler 对象创建

StatementHandler 是在哪里创建的呢?在手把手带你了解MyBatis的Executor执行器中,在执行doQuery和doUpdate方法时,都会创建StatementHandler对象。 以SimpleExecutor为例,创建StatementHandler对象实际由Configuration对象创建的。

// SimpleExecutor的doUpdate方法
@Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      // 1. 创建StatementHandler
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      // 2. 创建Statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 3. 执行sql操作
      return handler.update(stmt);
    } finally {
      // 2. 关闭Statement
      closeStatement(stmt);
    }
  }

ConfigurationnewStatementHandler方法中,创建的是RoutingStatementHandler对象。我们知道RoutingStatementHandler实际是对三种StatementHandler的一种包装。

// Configuration的newStatementHandler方法
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
 // 加载插件
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

继续点击去看,根据MappedStatement对象的类型,创建出具体的StatementHandler对象。如果MappedStatement没有指出具体的StatementType,那么StatementType默认是PREPARED类型的。

// 实际的处理器,SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler三种处理器中的一种
  private final StatementHandler delegate;

  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());
    }

  }

接下来,我们看看PreparedStatementHandler创建的过程,实际调用的是BaseStatementHandler的构造方法。

// PreparedStatementHandler的构造方法
 public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
   // 实际调用的是BaseStatementHandler的构造方法
   super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
 }

至此,我们了解到了,其实三种StatementHandler都是用的BaseStatementHandler的构造方法创建的。

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) { // issue #435, get the key before calculating the statement
      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);
  }
2.3 Statement对象创建

StatementHandler对象创建出来了,就可以创建Statement对象了。也是以SimpleExecutor执行器为例子。

      // 1. 创建StatementHandler
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      // 2. 创建Statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      ...

SimpleExecutorprepareStatement方法 中主要做了以下三步:

  1. 获取数据库连接
  2. 创建Statement对象
  3. 设置sql参数
// SimpleExecutor 的 prepareStatement方法
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 1. 获取数据库连接
    Connection connection = getConnection(statementLog);
    // 2. 创建Statement对象
    stmt = handler.prepare(connection, transaction.getTimeout());
    // 3. 设置sql参数
    handler.parameterize(stmt);
    return stmt;
  }

我们继续看看prepare()方法做了什么,这个方法BaseStatementHandler给出了默认实现,因此三个StatementHandler用的都是这个实现。主要做了以下工作

  1. 初始化Statement对象
  2. 设置超时时间
  3. 设置查询大小
  4. 出现异常关闭Statement对象
// BaseStatementHandler的prepare方法
 @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      // 1. 初始化Statement对象
      statement = instantiateStatement(connection);
      // 2. 设置超时时间
      setStatementTimeout(statement, transactionTimeout);
      // 3. 设置查询大小
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      // 4. 关闭Statement对象
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

可以Statement对象的初始化操作是在instantiateStatement方法中进行的,我们继续看看instantiateStatement这个方法又做了什么操作。好的,在BaseStatementHandlerinstantiateStatement方法被设计为抽象方法,由子类实现,这点也体现出了模板方法的设计模式。

// BaseStatementHandler的instantiateStatement方法
protected abstract Statement instantiateStatement(Connection connection) throws SQLException;

现在以SimpleStatementHandler为例子,最终调用的还是connection.createStatement()方法,回到了最初的起点,也就是MyBatis对JDBC操作进行了包装。

@Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    // 实际还是调用的connection.createStatement()方法
    if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      return connection.createStatement();
    } else {
      return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

获取到了Statement 对象,就可以快乐执行execute方法,向数据库发送sql语句执行了。

3. MappedStatement 对象

有眼尖的同学在前面会看到Executor在执行doUpdate的时候,会传入MappedStatement对象,那么MappedStatementStatementStatementHandler对象间有什么关联呢。 我们在用MyBatis配置sql的时候,insert/update/delete/select等标签下面都会包含一段sql,MappedStatement就是对sql标签的信息描述。

// 一个select标签会对应一个MappedStatement对象
 <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from t_test_user
    where id = #{id,jdbcType=BIGINT}
  </select>

MappedStatement 类的私有属性如下

 // mapper配置文件名,如:userMapper.xml
  private String resource;
  // 配置类
  private Configuration configuration;
  // 命名空间+标签id 如com.example.demo.dao.TTestUserMapper.selectByPrimaryKey
  private String id;
  private Integer fetchSize;
  // 超时时间
  private Integer timeout;
  // sql对象类型 STATEMENT, PREPARED, CALLABLE 三种之一
  private StatementType statementType;
  // 结果集类型
  private ResultSetType resultSetType;
  // sql语句
  private SqlSource sqlSource;
  // 缓存
  private Cache cache;
  // 参数映射关系
  private ParameterMap parameterMap;
  // 结果映射关系,可以自定义多个ResultMap
  private List<ResultMap> resultMaps;
  private boolean flushCacheRequired;
  // 是否启用缓存
  private boolean useCache;
  // 结果是否排序
  private boolean resultOrdered;
  // sql语句类型,INSERT, UPDATE, DELETE, SELECT
  private SqlCommandType sqlCommandType;
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;
  private boolean hasNestedResultMaps;
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  private String[] resultSets;

其中最主要的方法还是getBoundSql方法,对动态sql进行解析,获取最终的sql语句。关于SqlSourceBoundSql相关的内容我们将在其它文章中介绍。

  public BoundSql getBoundSql(Object parameterObject) {
    // 获取BoundSql对象,BoundSql对象是对动态sql的解析
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    // 获取参数映射
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

    return boundSql;
  }

4. 总结

这篇文章带大家详细了解Statement对象,StatementHandler以及其三种实现,还有MappedStatement也做了简单介绍,Statement对象作为主要组件,了解其创建和原理对我们整体了解MyBatis会很有帮助。