MyBatis基础(三) 底层调用JDBC--源码分析

系列内容回顾:

  • MyBatis基础(一) MyBatis入门与基本框架
  • MyBatis基础(二) 代理对象的生成–源码分析
  • MyBatis基础(三) 底层调用JDBC–源码分析

前面的文章分析了Mybatis是如何在给定接口的情况下, 代替我们生成代理对象的, 这篇文章接着带大家来学习Mybatis.

在之前提到, 无论是用代理生成Dao类还是自己手写Dao类, 都是用工厂方法获得一个SqlSession, 然后调用其select, update, insert方法; 我们也知道, 如果用最原始的JDBC的方法来写, 通常落到执行层面, 通常就是pstmt.executeUpdate, pstmt.executeQuery, pstmt.execute, stmt.execute这样的一些方法. 那么Mybatis中是如何代替我们用SqlSession的方法去操纵底层JDBC的, 这就是今天想分析的内容.

SqlSession

SqlSession是Mybatis中非常重要的接口, 通过定义了对数据库的常见操作的方法, 屏蔽了底层jdbc细节, 我们先看下该接口提供的方法都有哪些.
MyBatis基础(三) 底层调用JDBC--源码分析_第1张图片

可以看到, 除了commit, rollback这样的事务相关的方法, 大多数方法支持的是各种模式的CRUD. 我们可以将他们分为两个部分, 一个是select相关的, 一个是update相关的(update/delete/insert), 通过阅读源码可以发现后者中的方法最终都是对update(String statement, Object parameter)这样一个方法进行的封装; 而前者中提供的各种方法是对

  • select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler)
  • selectCursor(String statement, Object parameter, RowBounds rowBounds)
  • selectList(String statement, Object parameter, RowBounds rowBounds)

进行的封装. 因此接下来的重点就是上述的四个方法.

executor.update

update(String statement, Object parameter)方法的底层调用的是executor.update(MappedStatement ms, Object parameter)方法, executor 则是SqlSession中执行数据库操作的实际对象, 通过跟踪代码, 可以看到, executor是个一个接口, 里面封装了query和update两种sql statement的执行模型, 这个接口有的接口大致如下:
MyBatis基础(三) 底层调用JDBC--源码分析_第2张图片
在SqlSession中, 实际实际引入的executor的实现类是CachingExecutor, 但是注意到CachingExecutor本身是一个装饰器模式, 用来处理和查询缓存相关的工作, 其真正功能由成员Executor delegate完成, delegate是一个SimpleExecutor的实例, 这个类的结构很简单

/**
 * @author Clinton Begin
 */
public class SimpleExecutor extends BaseExecutor {
  public SimpleExecutor(Configuration configuration, Transaction transaction) {
  }
  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
  }
  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  }
  @Override
  protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
  }
  @Override
  public List<BatchResult> doFlushStatements(boolean isRollback) {
  }
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  }
}

甚至没有query方法, 因此我们可以想到, 它应该是继承了父类BaseExecutor的方法, 基类中的update方法如下

  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    clearLocalCache(); //使缓存失效
    return doUpdate(ms, parameter);
  }

update中, 方法调用了doUpdate方法, doUpdateBaseExecutor中是个抽象方法, 它的实际实现位于SimpleExecutor中,

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt); // 实际执行的preparedStatement的位置
    } finally {
      closeStatement(stmt);
    }
  }

继续跟着handler看下去, 可以看到, StatementHandler也是一个接口, 用于处理stmt应该如何执行, 它的接口如下:
MyBatis基础(三) 底层调用JDBC--源码分析_第3张图片
这里调用的是PreparedStatementHandler中的update方法, 这个方法如下:

  public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute(); // 这里看到了我们熟悉的jdbc方法, 也就是在这里实际调用了JDBC底层
    int rows = ps.getUpdateCount();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }

结论, 看到这里我们基本把SqlSession.update的调用关系理顺了, 是通过executor调用StatementHandler去执行实际的JDBC语句.

executor.query

query方法的返回结果通常是比较复杂的, 在xml配置中, 我们要么通过resultType要么通过resultMap指定如何解析, 这部分处理都在ResultHandler中进行. 此外查询需求中常见的需要对查询结果进行偏移, 这部分是由RowBounds这个类来表示. selectselectList在底层调用的都是executor.query方法

RowBounds

这个类比较简单, 它只包含两个成员变量, 默认情况是无偏移, limit为Integer.MAX_VALUE, 上述参数用一个常量对象DEFAULT来表示.

  public static final int NO_ROW_OFFSET = 0;
  public static final int NO_ROW_LIMIT = Integer.MAX_VALUE;
  public static final RowBounds DEFAULT = new RowBounds();
  
  private final int offset; // 查询偏移
  private final int limit; // 查询的条数限制

ResultHandler

这是一个结果处理的接口, 用于将结果重新映射成java对象, 它的结构如下
MyBatis基础(三) 底层调用JDBC--源码分析_第4张图片

BoundSql

这个类中包含了完成动态内容处理的实际的Sql语句, 可能会包括?这样的占位符以及占位符对应的parameter的列表, 列表的每个元素保存了占位符的某个位置的jdbc类型, java类型等信息, 还可以包括一些参数的额外信息, 比如要如何从实体类中读取数据填到placeholder中的映射关系.

和update很相似的, executor.select落到实处也是执行的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();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

在PreparedStatementHandler中, 对于query方法的实现也很直接了当,

  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute(); // 底层调用JDBC的方法
    return resultSetHandler.handleResultSets(ps);
  }

用jdbc执行后, 用resultSetHandler进行处理. 在这个对象的方法中调用了resultSetHandler对结果行进行解析, 并存储到对象. 这部分在之后可能会单独出一篇文章谈查询结果的解析, 限于篇幅的原因, 这里不再展开叙述.

你可能感兴趣的:(JavaEE知识点与源码分析)