MyBatis源码解析(二)——执行流程

目录

  • 前言
  • 1、MyBatis执行SQL入口
    • 1.1、Sql会话工厂——SqlSessionFactory
    • 1.2、Sql会话——SqlSession
    • 1.3、执行器——Executor
      • 1.3.1、缓存执行器——CachingExecutor
      • 1.3.2、基础执行器——BatchExecutor
  • 2、MyBatis获取结果集
      • 2.1、StatementHandler
      • 2.1.1、RoutingStatementHandler
      • 2.1.2、BaseStatementHandler
      • 2.1.3、SimpleStatementHandler
      • 2.1.4、PreparedStatementHandler
      • 2.1.5、CallableStatementHandler
    • 2.2、总结
  • 3、SQL 实参处理器
    • 3.1、ParameterHandler
    • 3.2、DefaultParameterHandler
  • 4、MyBatis处理结果集
    • 4.1、ResultSetHandler
    • 4.2、DefaultResultSetHandler
      • 4.2.1、getRowValue
      • 4.2.2、storeObject
  • 5、执行流程总结
  • 6、后记——MyBatis缓存一点释义
    • 6.1、一级缓存
    • 6.2、二级缓存
    • 6.3、缓存总结

前言

单独使用 MyBatis执行数据库的操作时,常见代码如下:

InputStream is = Resources.getResourceAsStream("spring/mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
SqlSession session = factory.openSession();
User user = session.selectOne("org.xxx.dao.IUserDao.queryUserInfoById", 1L);

在上一篇构建流程中,已经介绍了 SqlSessionFactoryBuilder 构建部分,接下来解析 SQL执行部分。构建完成后,生成了 SqlSession工厂(SqlSessionFactory),SqlSession工厂用来生成 SqlSession,SqlSession 主要用来执行命令,获取映射,管理事务。

下面先来看下 SqlSessionFactory。

1、MyBatis执行SQL入口

1.1、Sql会话工厂——SqlSessionFactory

SqlSessionFactory 是一个接口,默认实现为 DefaultSqlSessionFactory。

public class DefaultSqlSessionFactory implements SqlSessionFactory {

  private final Configuration configuration;

  public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
  }

  @Override
  // 生成SqlSession
  public SqlSession openSession() {
    // 传入defaultExecutorType,为ExecutorType.SIMPLE
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
  
  // 生成SqlSession
  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);
      // 新建执行器,执行器负责SQL语句的生成以及查询缓存的维护,默认是SimpleExecutor,使用的是Configuration中的newExecutor方法
      final Executor executor = configuration.newExecutor(tx, execType);
      // 生成SqlSession,为DefaultSqlSession
      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();
    }
  }
}
// Configuration类中新建执行器
public class Configuration {
  // 新建执行器
	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);
    }
    // 如果需要缓存,则使用缓存执行器
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    // 调用插件,通过插件可以改变Executor行为
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
}

新建执行器大致流程为:

  1. 调用 DefaultSqlSessionFactory 的 openSession 方法开始创建 SqlSession。
  2. 调用 Configuration 的 newExecutor方法新建执行器。如果指定了执行器类型(BATCH、REUSE、SIMPLE),则会创建对应类型的执行器,如果未指定,则默认为 SimpleExecutor。如果 Configuration 中指定了 cacheEnabled 属性为 true(即开启缓存),那么会将执行器封装为缓存执行器(CachingExecutor),在 mybatis-config.xml 文件中的 标签下 cachedEnabled 配置的,如果没有配置默认为 true,即默认开启缓存。
  3. 使用参数 configuration、executor 创建 DefaultSqlSession 对象(即默认SQL会话)。

1.2、Sql会话——SqlSession

// SQL会话,它是MyBatis的核心API, 主要用来执行命令, 获取映射, 管理事务, 接收开发人员提供的statement和参数, 执行并返回操作结果
public interface SqlSession extends Closeable {

	// 查询单个
  <T> T selectOne(String statement, Object parameter);

  // 查询集合
  <E> List<E> selectList(String statement, Object parameter);

  // 查询map
  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);

  // 查询游标
  <T> Cursor<T> selectCursor(String statement);
	...
    
}

public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;

  // 查询单个
  @Override
  public <T> T selectOne(String statement, Object parameter) {
    // 根据statement和参数查询集合
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

  @Override
  public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
    return this.selectMap(statement, null, mapKey, RowBounds.DEFAULT);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      // 根据statement id找到对应的mappedStatement
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 使用执行器查询结果,rowBounds参数不用管,是用来逻辑分页的,ResultHandler参数是null
      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();
    }
  }
  ...
}

我们需要创建一个 SQL会话来执行 CRUD 操作,MyBatis 中指的就是 SqlSession 接口,其中的方法定义了 CRUD 操作,默认实现类为 DefaultSqlSession。以查询集合 selectList 方法为例进行简单解析如下:

  1. 根据 statementId 去 Configuration 中获取对应的 MappedStatement 对象,就是一个 map 集合的 get 方法。
  2. 使用获取到的 MappedStatement 传入执行器,对参数 parameter 进行包装,调用执行器的 query方法进行查询并返回结果。

1.3、执行器——Executor

执行器 Executor 是一个接口,定义了数据库操作的基本方法。有实现类 CachingExecutor 和抽象实现类 BaseExecutor,之前新建执行器是三种执行器 SimpleExecutor、BatchExecutor、ReuseExecutor 都继承了 BaseExecutor,UML类图如下:
MyBatis源码解析(二)——执行流程_第1张图片
下面分析执行器中 query 方法的源码。

1.3.1、缓存执行器——CachingExecutor

在新建执行器流程中,默认开启缓存,我们先看一下 CachingExecutor 中的 query 方法。

public class CachingExecutor implements Executor {

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 步骤1:获取 BoundSql 对象,解析 BoundSql
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 创建查询语句对应的CacheKey对象
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 获取查询语句所在命名空间对应的二级缓存
    Cache cache = ms.getCache();
    // 步骤2:是否开启了二级缓存功能,默认情况下是没有开启缓存的(二级缓存),如果要开启二级缓存,需要在SQL映射文件中添加一行
    if (cache != null) {
      // 根据  节点配置的 flushCacheRequired 属性为 true 时,才会清空一级缓存
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      // 增加查询层数
      queryStack++;
      // 查询一级缓存
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        // 针对存储过程调用的处理,其功能是:在一级缓存命中时,获取缓存中保存的输出类型参数,并设置到用户传入的实参(parameter)对象中
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 未查到则去数据库查询
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      // 当前查询完成,查询层数减少
      queryStack--;
    }
    // 在最外层的查询结束时,所有嵌套查询也已经完全加载,所以这里可以触发 DeferredLoad 加载一级缓存中记录的嵌套查询的结果对象
    if (queryStack == 0) {
      // 加载 延迟加载队列中所有元素,deferredLoads:延迟加载队列
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // 清空延迟加载队列
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // 生命周期为 STATEMENT,清除一级缓存
        clearLocalCache();
      }
    }
    return list;
  }
  
  // 从数据库查询数据
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    // 标识 sql 查询
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      // 查询数据库,在具体的执行器中实现
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      // 移除标识
      localCache.removeObject(key);
    }
    // 将结果放入一级缓存
    localCache.putObject(key, list);
     // 存储过程
    if (ms.getStatementType() == StatementType.CALLABLE) {
      // 存储过程中 出参类型缓存
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
  ...
}

public class SimpleExecutor extends BaseExecutor {

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      // 获取配置对象configuration
      Configuration configuration = ms.getConfiguration();
      // 新建StatementHandler,它负责操作Statement对象与数据库进行交互
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 准备语句,这里就是原生JDBC代码创建 Statement 的过程
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 调用 StatementHandler.query() 方法,执行 SQL 语句,并通过 ResultSetHandler 完成结果集映射
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  ...
}

BaseExecutor 查询源码流程如下:

  1. 根据该 CacheKey 对象查找一级缓存,如果缓存命中则返回缓存中记录的结果对象。
  2. 如果缓存未命中则查询数据库得到结果集, 查询数据库的操作在其子类中实现。新建 StatementHandler 对象,准备查询语句,调用 StatementHandler 的 query方法查询数据库,然后将结果集映射成结果对象并保存到一级缓存中,同时返回结果对象。
  3. 加载延迟加载的对象,然后清空延迟加载队列。
  4. 生命周期为 STATEMENT,清除一级缓存。

2、MyBatis获取结果集

2.1、StatementHandler

StatementHandler 是一个接口,依赖 ParameterHandler 和 ResultSetHandler,定义了一些规范,有四个实现类,定义了具体的实现。

StatementHandler 是对 Statement 的一个封装,提供了 Statement 的所有功能,包括绑定执行的实参,批量执行 Sql语句,执行 select、update、delete、insert 语句功能,还有将结果集映射为成结果对象。

Statement 主要用于 sql 语句的执行和 sql 执行返回结果的处理。

源码如下:

public interface StatementHandler {

  // 从连接中获取一个 Statement
  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;

  // 绑定 statement 执行时所需的实参
  void parameterize(Statement statement)
      throws SQLException;

  // 批量执行 SQL 语句
  void batch(Statement statement)
      throws SQLException;

  // 执行 update/insert/delete 语句
  int update(Statement statement)
      throws SQLException;

  // 执行 select 语句
  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;

  // 执行 select 语句
  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;

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

  // 获取 ParameterHandler 对象
  ParameterHandler getParameterHandler();

}

UML类图如下:

MyBatis源码解析(二)——执行流程_第2张图片

2.1.1、RoutingStatementHandler

RoutingStatementHandler : 定义一个 StatementHandler 接口 代理对象,根据 statementType 类型,创建 StatementHandler 接口具体实现。在MyBatis工作时,使用的 StatementHandler 接口对象实际上就是 RoutingStatementHandler 对象。

public class RoutingStatementHandler implements StatementHandler {

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

2.1.2、BaseStatementHandler

BaseStatementHandler : 是实现 StatementHandler 接口的一个抽象类,用于简化 StatementHandler 接口实现的难度,它主要有三个子类:

  • SimpleStatementHandler: 管理 Statement 对象并向数据库中推送不需要预编译的SQL语句。
  • PreparedStatementHandler: 管理 Statement 对象并向数据中推送需要预编译的SQL语句。
  • CallableStatementHandler:管理 Statement 对象并调用数据库中的存储过程。

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

public abstract class BaseStatementHandler implements StatementHandler {

  protected final Configuration configuration;
  // 对象工厂,创建对象来使用
  protected final ObjectFactory objectFactory;
  // 类型转换注册器
  protected final TypeHandlerRegistry typeHandlerRegistry;
  // SQL 查询结果,封装成 ResultMap 对象 处理器
  protected final ResultSetHandler resultSetHandler;
  // 记录使用的 ParameterHandler 对象, ParameterHandler 的主要功能是为 SQL 句绑定实参 ,使用实参替换 SQL 语句的中 ? 占位符。
  protected final ParameterHandler parameterHandler;

  // sql 执行器
  protected final Executor executor;
  // 封装 mapper.xml  节点 成 MappedStatement 对象
  protected final MappedStatement mappedStatement;
  //  RowBounds 记录了用户设置的 offset limit ,用于在结果采集器中定位映射的起始位置和结束位置
  protected final RowBounds rowBounds;

  // 数据库可以执行的 SQL 语句
  protected BoundSql boundSql;

  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) {
      // 调用 KeyGenerator processBefore() 方法获取主键
      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.1.3、SimpleStatementHandler

SimpleStatementHandler 继承了 BaseStatementHandler 抽象类。它底层使用 java.sql.Statement 对象来完成数据库的相关操作。所以 SQL 语句中不能存在占位符。

public class SimpleStatementHandler extends BaseStatementHandler {

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 获取待执行的 SQL 语句
    String sql = boundSql.getSql();
    // 执行 SQL 语句
    statement.execute(sql);
    // 获取结果集 ResultSet,对结果集进行处理
    return resultSetHandler.<E>handleResultSets(statement);
  }
  
  @Override
  public void parameterize(Statement statement) throws SQLException {
    // N/A
  }
  
	...
}

2.1.4、PreparedStatementHandler

PreparedStatementHandler 底层依赖于 java.sql.PreparedStatement 对象来完成数据库的相关操作。在 SimpleStatementHandler.parameterize() 方法中, 会调用前面介绍的 ParameterHandler.setParameters() 方法完成 SQL 语句的参数绑定。

public class PreparedStatementHandler extends BaseStatementHandler {

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 类型强制转换
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行 SQL 语句
    ps.execute();
    // 获取结果集 ResultSet,对结果集进行处理
    return resultSetHandler.<E> handleResultSets(ps);
  }
  
  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    // 获取待执行的 SQL 语句
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      // 根据 MappedStatement.keyGenerator 字段的位,创建 PreparedStatement 对象
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        // 在 insert 语句执行完成之后,会将 keyColumn 指定的列对象
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
      // 设置结果集是否可以滚动以及其游标是否可以上下移动,设置结果集采集器是否可更新
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      // 创建普通的 PreparedStatement 对象
      return connection.prepareStatement(sql);
    }
  }

  @Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }
	...
}

2.1.5、CallableStatementHandler

CallableStatementHndler 底层依赖于 java.sql.CallableStatement 调用存储过程,其中 parameterize()方法也会调用 ParameterHandler.setParameters() 方法完 SQL 语句参数绑定,并指定输出参数的索引位置和 JDBC 类型。

public class CallableStatementHandler extends BaseStatementHandler {

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    CallableStatement cs = (CallableStatement) statement;
    cs.execute();
    List<E> resultList = resultSetHandler.<E>handleResultSets(cs);
    resultSetHandler.handleOutputParameters(cs);
    return resultList;
  }

  @Override
  public void parameterize(Statement statement) throws SQLException {
    registerOutputParameters((CallableStatement) statement);
    parameterHandler.setParameters((CallableStatement) statement);
  }

  //处理存储过程中的参数类型
  private void registerOutputParameters(CallableStatement cs) throws SQLException {
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    for (int i = 0, n = parameterMappings.size(); i < n; i++) {
      ParameterMapping parameterMapping = parameterMappings.get(i);
      if (parameterMapping.getMode() == ParameterMode.OUT || parameterMapping.getMode() == ParameterMode.INOUT) {
        if (null == parameterMapping.getJdbcType()) {
          throw new ExecutorException("The JDBC Type must be specified for output parameter.  Parameter: " + parameterMapping.getProperty());
        } else {
          if (parameterMapping.getNumericScale() != null && (parameterMapping.getJdbcType() == JdbcType.NUMERIC || parameterMapping.getJdbcType() == JdbcType.DECIMAL)) {
            cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE, parameterMapping.getNumericScale());
          } else {
            if (parameterMapping.getJdbcTypeName() == null) {
              cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE);
            } else {
              cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE, parameterMapping.getJdbcTypeName());
            }
          }
        }
      }
    }
  }
}

2.2、总结

综上所述,在第一章中调用到了 StatementHandler 的 query 方法,因为 StatementHandler 是一个接口,所以真正调用到的是 RoutingStatementHandler 的 query 方法,由于有传入参数,会调用到 PreparedStatementHandler 中的 query 方法,流程如下:

  1. 将 statement 语句进行类型强制转换,转化为 PreparedStatement。
  2. 调用 PreparedStatement 的 execute 方法执行 Sql 语句,里面是 JDBC 的执行方法。
  3. 获取结果集 ResultSet,对结果集进行处理。

3、SQL 实参处理器

3.1、ParameterHandler

ParameterHandler 是一个 SQL 实参处理器,主要功能是为 SQL 语句绑定实参,使用传入的实参替换 SQL 语句的中 ? 占位符。

public interface ParameterHandler {

  Object getParameterObject();

  // 设置参数
  void setParameters(PreparedStatement ps)
      throws SQLException;
}

3.2、DefaultParameterHandler

DefaultParameterHandler 是 ParameterHandler 接口唯一默认实现类。

在 BoundSql 中记录的 SQL 语句中可能包含 “?” 占位符,而每个 “?” 占位符都对应了 BoundSql.parameterMappings 集合中的一个元素,在该 ParameterMapping 中记录了对应的参数名称以及该参数的相关属性。

public class DefaultParameterHandler implements ParameterHandler {

  // TypeHandlerRegistry 对象,管理 MyBatis 中的 全部 TypeHandler 对象
  private final TypeHandlerRegistry typeHandlerRegistry;
  // MappedSt tement 对象,其中记录 SQL 节点相应的配置信息
  private final MappedStatement mappedStatement;
  // 用户传入的实参对象
  private final Object parameterObject;
  // 包含 "?" 占位符的,包含数据库将要执行的 sql 语句的对象
  private final BoundSql boundSql;
  private final Configuration configuration;
  
	...

  /**
   * 将 BoundSql 中,含有 "?" 占位符替换为实参。
   * setParameters 会遍历 BoundSql.parameterMappings 集合中记录的 ParameterMapping 对象,井根据其中记录的参数名称查找相应实参 然后与 SQL 语句绑定。
   */
  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 取出 sql 中的参数映射列表
    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) {
          // 记录绑定的实参
          Object value;
          // 获取参数名称
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) {
            // 获取参数名称
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            //  整个实参为空
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            // 实参可以直接通过 TypeHandler 转换成 JdbcType
            value = parameterObject;
          } else {
            // 获取对象中相应的属性位或查找 Map 对象中位
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          // 获取 ParameterMapping 中设置的 TypeHandler 对象
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            // 通过 TypeHandler setParameter() 方法会调用 PreparedStatement.set *() 方法,为 SQL 语句绑定相应的实参
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }
}

4、MyBatis处理结果集

4.1、ResultSetHandler

ResultSetHandler 是一个接口,主要有两个功能,处理 Statement 执行后产生的结果集,生成结果列表,以及处理存储过程执行后的输出参数。

public interface ResultSetHandler {

  // 处理集合结果集
  <E> List<E> handleResultSets(Statement stmt) throws SQLException;

  // 处理游标结果集
  <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;

  // 处理存储过程执行后的输出参数
  void handleOutputParameters(CallableStatement cs) throws SQLException;

}

4.2、DefaultResultSetHandler

DefaultResultSetHandler 是 ResultSetHandler 的默认实现类,它按照 Mapper 文件中配置的 ResultType 或 ResultMap 来封装成对应的对象,最后将封装的对象返回即可。

之前分析到通过 JDBC 获取到结果集 ResultSet 了,需要进一步做结果集的类型处理,下面来看处理结果集的源码:

public class DefaultResultSetHandler implements ResultSetHandler {

  // 处理集合结果集
  @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    // 标签的resultSets属性,一般不使用 不用管
    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }
	  // 如果只有一个结果集合,则直接从多结果集中取出第一个
    return collapseSingleResultList(multipleResults);
  }
  
  // 处理结果集
  private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
          // 如果没有 resultHandler,新建 DefaultResultHandler
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          // 调用自己的 handleRowValues
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          // 得到记录的list
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          // 如果有 resultHandler,调用 handleRowValues
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // 最后关闭结果集
      closeResultSet(rsw.getResultSet());
    }
  }
  
  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    // 是否有内嵌,这里不用管
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      // 继续处理
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }
  
  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
    // 获取结果集信息,使用rowBounds的分页信息,进行逻辑分页(即在内存中分页)
    skipRows(rsw.getResultSet(), rowBounds);
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
      // 通过标签的子标签对结果映射进行鉴别
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
      // 将查询结果封装到POJO中,rsw 是结果集的包装类,里面封装了返回结果的信息,通过结果集包装类创建 POJO
      Object rowValue = getRowValue(rsw, discriminatedResultMap);
      // 处理对象嵌套的映射关系
      storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }
  }
}

大致流程如下:

  1. 判断 resultHandler 是否为空,如果为空则生成一个默认的 DefaultResultHandler,不为空则使用原本的,最终都会去调用 handleRowValues 方法进行进一步的处理。
  2. handleRowValues 方法调用 getRowValue 方法,将查询结果封装到 POJO 中。
  3. handleRowValues 方法调用 storeObject 方法,将获取到的 POJO 对象加入到 resultHandler 中的列表字段,最终加入到默认的结果集列表 multipleResults 中。

4.2.1、getRowValue

getRowValue 方法就是通过结果集包装类和一开始定义的类字段( POJO 中的字段) 封装成一个 Object 实体类。

// 取一行的值
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
  // 实例化ResultLoaderMap(延迟加载器)
  final ResultLoaderMap lazyLoader = new ResultLoaderMap();
  // 调用自己的createResultObject,内部就是new一个对象(如果是简单类型,new完也把值赋进去)
  Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
  if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
    // 一般不是简单类型不会有typehandler,这个if会进来,重新有做了封装,这步之后 metaObject 就持有了 resultObject
    final MetaObject metaObject = configuration.newMetaObject(rowValue);
    boolean foundValues = this.useConstructorMappings;
    if (shouldApplyAutomaticMappings(resultMap, false)) {
      // 自动映射,这里是赋值操作,也就是说此时我们的 resultHandler 其中的值都是 null 或默认值,这里把每个列的值都赋到相应的字段里去了
      foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
    }
    // 执行完这个方法 resultObject 中的字段才有值,这里传入的其实是 metaObject
    foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
    foundValues = lazyLoader.size() > 0 || foundValues;
    rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
  }
  return rowValue;
}

// 给每一列赋值
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
  throws SQLException {
  // 获取字段名称列表
  final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
  boolean foundValues = false;
  final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
  // 循环赋值
  for (ResultMapping propertyMapping : propertyMappings) {
    String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
    if (propertyMapping.getNestedResultMapId() != null) {
      // the user added a column attribute to a nested result map, ignore it
      column = null;
    }
    if (propertyMapping.isCompositeResult()
        || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
        || propertyMapping.getResultSet() != null) {
      // 从结果集包装类里面获取每个字段的值,涉及到一个很重要的类 TypeHandler
      Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
      // issue #541 make property optional
      final String property = propertyMapping.getProperty();
      if (property == null) {
        continue;
      } else if (value == DEFERED) {
        foundValues = true;
        continue;
      }
      if (value != null) {
        foundValues = true;
      }
      if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
        // 在这里赋值了,其实这里面是给 metaObject 持有的 resultObject中的字段赋值
        metaObject.setValue(property, value);
      }
    }
  }
  return foundValues;
}

private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
  throws SQLException {
  if (propertyMapping.getNestedQueryId() != null) {
    return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
  } else if (propertyMapping.getResultSet() != null) {
    addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?
    return DEFERED;
  } else {
    // 前面的一些可以不用看,这里获取 ResultMapping 原本的 TypeHandler,然后通过 TypeHandler 这个类型处理器去处理值
    final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
    final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
    return typeHandler.getResult(rs, column);
  }
}

大致流程如下:

  1. 先定义一个 POJO 对象的空壳(即内部字段没有值)rowValue,使用该实例构造 metaObject 实例,metaObject 里面是持有了 rowValue。
  2. 调用 applyPropertyMappings 方法给 POJO 对象的字段赋值。先获取 POJO 类中字段的一些名称,然后通过结果集包装类 ResultSetWarpper 获取结果集中对应字段的值并且赋值给 metaObject 中持有的 rowValue “空壳”对象中的相应字段。赋值过程中的类型转换是通过 TypeHandler 来实现的。
  3. 通过步骤2,POJO 对象不再是空壳对象,返回 rowValue。

4.2.2、storeObject

storeObject 方法就是将获取到的 POJO 对象加入到 resultHandler 中的列表字段,最终加入到 multipleResults。

private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
  if (parentMapping != null) {
    linkToParents(rs, parentMapping, rowValue);
  } else {
    // 在这里处理
    callResultHandler(resultHandler, resultContext, rowValue);
  }
}

private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
  resultContext.nextResultObject(rowValue);
  // resultContext 封装了一些上下文信息 这里包括封装好的 Object,调用 ResultHandler 对象的 handleResult 方法
  ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}

// DefaultResultHandler 列表字段进行添加对象
public void handleResult(ResultContext<? extends Object> context) {
  // 就是将获取到的对象加入到 List
  list.add(context.getResultObject());
}

流程很简单,就是将之前生成的 POJO 对象添加到 ResultHandler 中的 list 字段中。

5、执行流程总结

MyBatis源码解析(二)——执行流程_第3张图片

执行流程如下:

  1. 用户执行查询操作,调用 SqlSession 的 select 相关方法。
  2. SqlSession 从 Configuration 中获取相应的 MappedStatement,然后调用执行器的 query方法。
  3. 默认先调用缓存执行器(CachingExecutor),做一些缓存处理,然后会调用到 CachingExecutor 的 queryFromDatabase 方法查询数据库,然后调用 SimpleExecutor 的 doQuery 方法,在该方法中创建 StatementHandler,最后调用 StatementHandler 的 query 方法(其中还通过 StatementHandler 生成 Statement,然后在执行方法中传入 Statement)。
  4. 将 Statement 转换为相应的类型(如 PreparedStatementHandler),然后调用 Statement 的execute 方法,即 JDBC 的查询操作。执行之前调用 ParameterHandler 的 setParameters 方法,为SQL语句绑定实参,使用传入的实参替换 SQL语句的中 ? 占位符。执行之后调用 ResultSetHandler 的 handleResultSets 处理查询到的结果集。
  5. 处理结果集过程中,生成 Statement 的封装类,然后取出 MapppedStatement 中的 ResultMap 并和生成的封装类去处理结果集,其中会生成一个 List对象,然后继续处理结果集,并在 handleResultSet 方法中生成 DefaultResultHandler(持有一个list字段),并通过 ResultSetWrapper 和 ResultMap 生成对应的 POJO 对象加入 DefaultResultHandler 的 list 字段和默认的结果集列表multipleResults。
  6. 最后调用 DefaultResultSetHandler 中的 collapseSingleResultList 方法去处理已经填充好的 multipleResults,如果 size 为1则返回列表中第一个,否则返回这个列表。

6、后记——MyBatis缓存一点释义

6.1、一级缓存

使用一级缓存,需要在MyBatis配置文件中配置以下语句。该语句共有两个选项,SESSION 或者STATEMENT,默认是 SESSION 级别,即在一个MyBatis会话中执行的所有语句,都会共享这一个缓存。一种是 STATEMENT 级别,缓存只对当前执行的这一个 Statement 有效。

<setting name="localCacheScope" value="SESSION"/>

一级缓存的特点:

  1. MyBatis 一级缓存的生命周期和 SqlSession 一致,增删改会清除一级缓存。
  2. MyBatis 一级缓存内部设计简单,只是一个没有容量限定的 HashMap,在缓存的功能性上有所欠缺。
  3. MyBatis 的一级缓存最大范围是 SqlSession 内部,有多个 SqlSession 或者分布式的环境下,数据库写操作会引起脏数据。即其他 SqlSession 对数据进行了修改,再次查询时读到了脏数据。建议设定缓存级别为 Statement。

6.2、二级缓存

二级缓存作用范围为 mapper 级别,即同一个 namespace 下的语句,都可以使用到二级缓存,其中增删改会清除二级缓存和一级缓存。开启二级缓存后,执行查询的流程就是:二级缓存 -> 一级缓存 -> 数据库。使用二级缓存,需要在 mapper 映射文件中配置以下语句:

<cache/>

有其他自定义配置,如:size(最多缓存对象的个数)、readOnly(是否只读,若配置可读写,则需要对应的实体类能够序列化)、blocking(若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存)等。

二级缓存的特点:

  1. MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度能够到 namespace 级别。
  2. MyBatis在多表关联查询时,极大可能会出现脏数据。由于是 namespace 级别,在多表关联查询时,其他的 namespace 对表进行了修改,会出现脏数据。
  3. 在分布式环境下,由于默认的 MyBatis 缓存实现是基于本地的,分布式环境下必然会出现读取到脏数据,直接使用 Redis、Memcached 等分布式缓存可能成本更低,安全性也更高。

6.3、缓存总结

由于一级缓存和二级缓存都有所缺陷,建议MyBatis缓存在生产环境中关闭,MyBatis单纯作为一个ORM框架使用更为合适。

你可能感兴趣的:(MyBatis源码解析,mybatis,java,mysql)