Emove

  • 首页
  • 归档
  • 分类
  • 标签

  • 搜索
context 反射 channel LRU BeanDefinition JVM 装饰者模式 MyBatis 快慢指针 归并排序 链表 hash表 栈 回溯 贪心 主从复制 二分查找 双指针 动态规划 AOF RDB 规范 BASE理论 CAP B树 RocketMQ Sentinel Ribbon Eureka 命令模式 访问者模式 迭代器模式 中介者模式 备忘录模式 解释器模式 状态模式 策略模式 职责链模式 模板方法模式 代理模式 享元模式 桥接模式 外观模式 组合模式 适配器模式 建造者模式 原型模式 工场模式 单例 UML 锁 事务 sql 索引

深入理解MyBatis-Statement执行体系

发表于 2020-09-05 | 分类于 MyBatis源码分析 | 0 | 阅读次数 102

深入理解MyBatis - Statement执行体系


前言

​ StatementHandler负责处理Mybatis与JDBC之间Statement的交互。在这个章节中,还会涉及到ParameterHandler和ResultSetHandler两个重要对象。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继承关系

​ 看到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方法中,感兴趣的同学自行去翻看。

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

2、ParameterHandler

​ ParameterHandler接口比较简单,只有getParameterObject和setParameters两个方法,前者用于获取参数,后者用于设置参数。这个接口也只有一个默认实现类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);
  }

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

StatemnetHandler执行流程图

# context # 反射 # channel # LRU # BeanDefinition # JVM # 装饰者模式 # MyBatis # 快慢指针 # 归并排序 # 链表 # hash表 # 栈 # 回溯 # 贪心 # 主从复制 # 二分查找 # 双指针 # 动态规划 # AOF # RDB # 规范 # BASE理论 # CAP # B树 # RocketMQ # Sentinel # Ribbon # Eureka # 命令模式 # 访问者模式 # 迭代器模式 # 中介者模式 # 备忘录模式 # 解释器模式 # 状态模式 # 策略模式 # 职责链模式 # 模板方法模式 # 代理模式 # 享元模式 # 桥接模式 # 外观模式 # 组合模式 # 适配器模式 # 建造者模式 # 原型模式 # 工场模式 # 单例 # UML # 锁 # 事务 # sql # 索引
深入理解MyBatis-二级缓存
JVM内存区域
  • 文章目录
  •   |  
  • 概览
林亦庭

林亦庭

less can be more

87 文章
11 分类
54 标签
RSS
Github
Creative Commons
© 2021 林亦庭
粤ICP备20029050号