Mybatis源码分析(四)Mybatis执行sql的四大组件

1,869 阅读8分钟

本文源代码来源于mybatis-spring-boot-starter的2.1.2版本

  SQL语句的执行涉及各个组件,其中比较重要的是ExecutorStatementHandlerParameterHandlerResultSetHandler

  Executor对象在创建Configuration对象的时候创建,并且缓存在Configuration对象里,负责管理一级缓存和二级缓存,并提供是事务管理的相关操作。Executor对象的主要功能是调用StatementHandler访问数据库,并将查询结果存入缓存中(如果配置了缓存的话)。StatementHandler首先通过ParammeterHandler完成SQL的实参绑定,然后通过java.sql.Statement对象执行sql语句并得到结果集ResultSet,最后通过ResultSetHandler完成结果集的映射,得到对象并返回。

Executor

1.1 类图

每一个 SqlSession 都会拥有一个Executor 对象,这个对象负责增删改查的具体操作,我们可以简单的将它理解为 JDBC 中 Statement 的封装版。也可以理解为 SQL 的执行引擎,要干活总得有一个发起人吧,可以把 Executor 理解为发起人的角色。

如上图所示,位于继承体系最顶层的是Executor执行器,它有两个实现类,分别是和BaseExecutorCachingExecutor

  • BaseExecutor

  BaseExecutor是一个抽象类,这种通过抽象的实现接口的方式是的体现,是Executor 的默认实现,实现了大部分 Executor 接口定义的功能,关于查询更新的具体实现由其子类实现。️BaseExecutor 的子类有四个,分别是 SimpleExecutorReuseExecutorCloseExecutorBatchExecutor

  1. SimpleExecutor

  简单执行器,是 MyBatis 中默认使用的执行器,每执行一次 update 或 select,就开启一个Statement 对象,用完就直接关闭 Statement 对象(可以是 Statement 或者是 PreparedStatment 对象)

  1. ReuseExecutor

  可重用执行器,这里的重用指的是重复使用Statement,它会在内部使用一个 Map 把创建的Statement 都缓存起来,每次执行 SQL 命令的时候,都会去判断是否存在基于该 SQL 的 Statement 对象,如果存在Statement 对象并且对应的 connection 还没有关闭的情况下就继续使用之前的 Statement 对象,并将其缓存起来。因为每一个 SqlSession都有一个新的 Executor对象,所以我们缓存在 ReuseExecutor 上的 Statement作用域是同一个 SqlSession

  1. CloseExecutor

  表示一个已关闭的Executor执行期

  1. BatchExecutor

  批处理执行器,用于将多个 SQL 一次性输出到数据库

  • CachingExecutor

  缓存执行器,先从缓存中查询结果,如果存在就返回之前的结果;如果不存在,再委托给Executor delegate 去数据库中取,delegate 可以是上面任何一个执行器。

1.2 Executor的选择和创建

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //创建Executor
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

SqlSessionFactory通过Configuration来创建sqlSession,Executor也是在这个时候初始化,我们来看下configuration.newExecutor()这个方法:

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
    //不指定就用简单执行器
      executor = new SimpleExecutor(this, transaction);
    }
    //如果cacheEnabled为true,则创建CachingExecutor,然后在其内部持有上面创建的Executor
    //cacheEnabled默认为true,则默认创建的Executor为CachingExecutor,并且其内部包裹着SimpleExecutor。
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    //使用InterceptorChain.pluginAll为executor创建代理对象。Mybatis插件机制。
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

ExecutorType类型可以通过xml标签和JavaApi进行赋值,默认为ExecutorType.SIMPLE

Mybatis插件机制会在其他系列文章里面讲解,这里就不过多介绍了。

1.3 Executor的执行流程

我们从SqlSession的selectList方法入手,其实他们的调用链路都差不多。

@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

上面说到cacheEnabled默认为true,则默认创建的Executor为CachingExecutor,并且其内部包裹着SimpleExecutor。所以这里executor还是CachingExecutor,我们来看下CachingExecutor的实现

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
      //是否有缓存
    Cache cache = ms.getCache();
    if (cache != null) {
    //创建缓存
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
            //执行SimpleExecutor的query方法
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //执行SimpleExecutor的query方法
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

这里的delegate就是SimpleExecutor,也就是说最终还是会执行SimpleExecutor的query实现。到这里,执行器所做的工作就完事了,Executor 会把后续的工作交给继续执行。下面我们来认识一下StatementHandler

StatementHandler

StatementHandler主要负责操作Statement对象与数据库进行交互,在这里我们先回顾一下原生JDBC的相关知识:

        //1 注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2 获得连接
        String url = "jdbc:mysql://localhost:3306/test";
        Connection conn = DriverManager.getConnection(url,"root", "root");
        //3获得语句执行者
        Statement st = conn.createStatement();
        //4执行SQL语句
        ResultSet rs = st.executeQuery("select * from role");
        //5处理结果集
        while(rs.next()){
            // 获得一行数据
            Integer id = rs.getInt("id");
            String name = rs.getString("name");
            System.out.println(id + " , " + name);
        }
        //6释放资源
        rs.close();
        st.close();
        conn.close();

JDBC通过java.sql.Connection对象创建Statement对象,Statement对象的execute方法就是执行SQL语句的入口。那么对于Mybtais的StatementHandler是用于管理Statement对象的。

2.1 类图

有木有感觉这个继承关系和Executor极为相似,顶层负接口分别有两个实现BaseStatementHandlerRoutingStatementHandler,而BaseStatementHandler分别有三个实现类SimpleStatementHandlerPreparedStatementHandlerCallableStatementHandler

我们不妨先来看下StatementHandler定义的方法:

ublic interface StatementHandler {
    //创建Statement对象,即该方法会通过Connection对象创建Statement对象。
  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;
    //对Statement对象参数化,特别是PreapreStatement对象。
  void parameterize(Statement statement)
      throws SQLException;
    //批量执行SQL
  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;
    // 获取SQl语句
  BoundSql getBoundSql();
    //获取参数处理器
  ParameterHandler getParameterHandler();

}
  • BaseStatementHandler

  它本身是一个抽象类,用于简化StatementHandler 接口实现的难度,属于适配器设计模式体现,它主要有三个实现类:

  1. SimpleStatementHandler

  java.sql.Statement对象创建处理器,管理 Statement 对象并向数据库中推送不需要预编译的SQL语句。

  1. PreparedStatementHandler

  java.sql.PrepareStatement对象的创建处理器,管理Statement对象并向数据中推送需要预编译的SQL语句。

  注意SimpleStatementHandlerPreparedStatementHandler 的区别是 SQL 语句是否包含变量。是否通过外部进行参数传入。SimpleStatementHandler 用于执行没有任何参数传入的 SQL,PreparedStatementHandler 需要对外部传入的变量和参数进行提前参数绑定和赋值。

  1. CallableStatementHandler

  java.sql.CallableStatement对象的创建处理器,管理 Statement 对象并调用数据库中的存储过程。

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

  }

  与CachExecutor相似,RoutingStatementHandler 并没有对 Statement 对象进行使用,只是根据StatementType 来创建一个代理,代理的就是对应Handler的三种实现类。在MyBatis工作时,使用的StatementHandler 接口对象实际上就是 RoutingStatementHandler对象。

2.2 StatementHandler的选择和创建

以查询为例,前面说到Executor在执行时会先查询缓存在走数据库,我们顺着**queryFromDatabase()方法的doQuery()**方法可以发现:

 @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
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

2.2.1 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;
  }

这里会创建一个RoutingStatementHandler对象,我们刚才说过了,它会根据StatementType来创建对应的Statement对象。StatementType是MappedStatement的一个属性,他在bulid的时候默认为StatementType.PREPAREDPreparedStatementHandler在构建的时候会调用其父类BaseStatementHandlerde的构造函数。ParameterHandlerResultSetHandler也是在这里创建的,我们来看看。

2.2.2 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;
    //构建parameterHandler
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    //构建resultSetHandler
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }

ParameterHandler

3.1 类图

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

  • getParameterObject:用于读取参数

  • setParameters: 用于对 PreparedStatement 的参数赋值

3.2 ParameterHandler的创建

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

ResultSetHandler

4.1 类图

ResultSetHandler 也很简单,它只有一个实现类DefaultResultSetHandler,主要负责处理两件事

  • 处理 Statement 执行后产生的结果集,生成结果列表。

  • 处理存储过程执行后的输出参数

4.2 ResultSetHandler的创建

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

本文仅对Mybatis执行sql的组件做一个入门级的介绍,后面我们会对执行sql的过程作出详细的讲解。