跳至主要內容

MyBatis流程(第三阶段)

IT王小二约 1737 字

MyBatis流程(第三阶段)

作者:IT王小二

博客:https://itwxe.comopen in new window

数据访问阶段:通过 SqlSession 完成 SQL 的解析,参数的映射、SQL 的执行、结果的解析过程。

    // --------------------第三阶段---------------------------
    // 4.执行查询语句并返回单条数据
    TUser user = mapper.selectByPrimaryKey(2);

一、关于Executor组件

在第二阶段的文章中,说到 Sqlsession 的功能最后都要交给 Executor 来执行,Executor 是 MyBaits 核心接口之一,定义了数据库操作最基本的方法,在其内部遵循 JDBC 规范完成对数据库的访问。

Executor 类继承机构如下图所示:

MyBatis流程(第三阶段)关于Executor组件

  • Executor:MyBaits 核心接口之一,定义了数据库操作最基本的方法。
  • CacheingExecutor:使用 装饰器模式,对真正提供数据库查询的 Executor 增强了二级缓存的能力,二级缓存初始化位置 DefaultSqlSessionFactory.openSessionFromDataSource(ExecutorType, TransactionIsolationLevel, boolean)
  • BaseExecutor:抽象类,实现了 Executor 接口的大部分方法,主要提供了缓存管理和事务管理的能力,其他子类需要实现的抽象方法为:doUpdate,doQuery 等方法。
  • BatchExecutor:批量执行所有更新语句,基于 jdbc 的 batch 操作实现批处理。
  • SimpleExecutor:默认执行器,每次执行都会创建一个 statement,用完后关闭。
  • ReuseExecutor:可重用执行器,将 statement 存入 map 中,操作 map 中的 statement 而不会重复创建 statement 。

二、Executor中的模板模式

模板模式:即父类定义了一个固定的流程,但是其中的某些流程由子类来选择具体实现。

MyBatis 的 执 行 器 组 件 是 使 用 模 板 模 式 的 典 型 应 用 , 其 中 BaseExecutor、BaseStatementHandler 是模板模式的最佳实践;BaseExecutor 执行器抽象类,实现了 Executor 接口的大部分方法,主要提供了缓存管理和事务管理的能力,其他子类需要实现的抽象方法为:doUpdate,doQuery 等方法。

接下来看一下 SqlSession 执行 selectOne 方法的一个代码分析流程图示:

MyBatis流程(第三阶段)查询流程模板模式

其中,查询数据库获得结果Result 这个过程中的 doQuery(ms, parameter, rowBounds, resultHandler, boundSql); 就是需要子类具体的实现的。

doQuery 方法是查询数据的结果的子步骤,doQuery 方法有 SIMPLE、REUSER、BATCH 三种实现,这三种不同的实现是在子类中定义的。

  • SimpleExecutor:默认配置,在 doQuery 方法中使用 PrepareStatement 对象访问数据库,每次访问都要创建新的 PrepareStatement 对象。
  • ReuseExecutor:在 doQuery 方法中,使用预编译 PrepareStatement 对象访问数据库,访问时,会重用缓存中的 statement 对象,其中使用了一个Map来存储(sql为key,statement为value)已经使用过的 statement 。
  • BatchExecutor:在 doQuery 方法中,实现批量执行多条 SQL 语句的能力。

三、Executor的三个重要小弟

上述流程中,说到了doQuery 这个方法是由子类来具体实现的,默认情况下我们使用 SimpleExecutor 那么 SimpleExecutor 又是怎么工作的呢?跟着 SimpleExecutor 中的 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();//获取configuration对象
      // 创建StatementHandler对象,
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // StatementHandler对象创建stmt,并使用parameterHandler对占位符进行处理
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 通过statementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

总体流程分析图示:

MyBatis流程(第三阶段)Executor执行过程

其实它下面有三个 handler,使用他们来完成sql参数占位符处理,查询数据库,结果集封装处理,这三个类都是在 Configuration 中实例化的,他们分别是:

  • StatementHandler:它的作用是使用数据库的 Statement 或 PrepareStatement 执行操作,启承上启下作用。
  • ParameterHandler:对预编译的 SQL 语句进行参数设置,SQL 语句中的的占位符“?”都对应 BoundSql.parameterMappings 集合中的一个元素,在该对象中记录了对应的参数名称以及该参数的相关属性。
  • ResultSetHandler:对数据库返回的结果集(ResultSet)进行封装,返回用户指定的实体类型。

1. StatementHandler

StatementHandler 完成 Mybatis 最核心的工作,也是 Executor 实现的基础,功能包括:创建 statement 对象,为 sql 语句绑定参数,执行增删改查等 SQL 语句、将结果映射集进行转化,StatementHandler 的类继承关系如下图所示:

MyBatis流程(第三阶段)StatementHandler类图

  • RoutingStatementHandler:通过名字可以看出来是一个路由功能相关的类,那么它到底干了什么呢,这个类也是在 Configuration 里面实例化的,代码如下:
  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 创建RoutingStatementHandler对象,实际由statmentType来指定真实的StatementHandler来实现
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }
  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // RoutingStatementHandler最主要的功能就是根据mappedStatment的配置,生成一个对应的StatementHandler对象并赋值给delegate
    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());
  }
  • BaseStatementHandler:所有子类的抽象父类,定义了初始化 statement 的操作顺序,由子类实现具体的实例化不同的 statement(模板模式)。
    • SimpleStatmentHandler:使用 statement 对象访问数据库,无须参数化。
    • PreparedStatmentHandler:使用预编译 PrepareStatement 对象访问数据库,通常情况下是这个。
    • CallableStatmentHandler:调用存储过程。
  /**
   * 使用模板模式,定义了获取Statement的步骤,其子类实现实例化Statement的具体的方式
   */
  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      // 通过不同的子类实例化不同的Statement,分为三类:simple(statment)、prepare(prepareStatement)、callable(CallableStatementHandler)
      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);
    }
  }

2. ParameterHandler

对预编译的 SQL 语句进行参数设置,SQL 语句中的的占位符“?”都对应 BoundSql.parameterMappings 集合中的一个元素,在该对象中记录了对应的参数名称以及该参数的相关属性。

这个代码可以跟着方法往下跟踪,并不难懂。

3. ResultSetHandler

对数据库返回的结果集(ResultSet)进行封装,返回用户指定的实体类型。

这个过程代码不难懂,但是过程比较繁杂,这个步骤大概有三个步骤:

找到映射匹配规则 -> 反射实例化目标对象 -> 根据规则填充属性值