MyBatis源码调试

1,702 阅读8分钟

1、调试说明

1.1环境

  • IntelliJ IDEA 2019.2.4 (Ultimate Edition)

1.2测试源码所在包

test 文件夹下的 org.apache.ibatis.submitted.call_setters_on_nulls_again

1.3目的

梳理 mybatis 进行查询时的执行流程。

2、官方测试代码

2.1说明

@BeforeAll 注解用于表示在当前测试类的所有测试方法之前执行注解该注解标注的方法,第一段代码将在执行@Test注解的方法之前设置 sqlSessionFactory。

2.2断点位置

在上述标注1、2、3的代码处打上断点,点击@Test左边的绿色按钮,选择Debug进行调试。

3、Mybatis查询执行流程图

查询执行流程图如下所示: 时序图

4、具体流程中的代码

4.1说明

  • 代码上方的语句含义为: 方法名:行号,类名称(包名)

  • 按照流程执行顺序显示代码。

4.2执行流程从左往右

4.2.1SqlSessionFactoryBuilder

build:36, SqlSessionFactoryBuilder (org.apache.ibatis.session)

public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }

上述方法用于创建 SqlSessionFactory,具体将调用如下方法。

build:49, SqlSessionFactoryBuilder (org.apache.ibatis.session)

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

上述方法根据传入的 reader、 environment、 properties 参数进行 XMLConfigBuilder 的实例化,并通过parser.parse() 方法生成 Configuration 对象。

build:92, SqlSessionFactoryBuilder (org.apache.ibatis.session)

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

上述方法调用了默认的 DefaultSqlSessionFactory 并根据 Configuration 对象进行 SqlSessionFactory 的创建。

4.2.2DefaultSqlSessionFactory

:41, DefaultSqlSessionFactory (org.apache.ibatis.session.defaults)

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

上述方法为 Configuration 对象为参数的 DefaultSqlSessionFactory 构造函数。

openSession:47, DefaultSqlSessionFactory (org.apache.ibatis.session.defaults)

@Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

上述方法将创建 SqlSession 实例,具体见如下方法 ,openSessionFromDataSource参数中的 ExecutorType 为默认的 SIMPLE,TransactionIsolationLevel 为 null,autoCommit 为 false。

openSessionFromDataSource:91, DefaultSqlSessionFactory (org.apache.ibatis.session.defaults)

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

上述方法内部主要有如下步骤:

  • 利用 Configuration 对象获取 Environment 对象
  • 根据 Environment 对象创建 TransactionFactory 对象
  • 利用上述创建的 TransactionFactory 对象创建一个 Transaction 对象
  • 利用 Configuration 对象创建Executor对象
  • 调用 DefaultSqlSession 的有参构造函数创建 SqlSession

创建Executor对象的方法如下:

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) interceptorChain.pluginAll(executor);
    return executor;
  }

上述方法虽然传入的 ExecutorType 为 SIMPLE,但是 cacheEnabled 由于是 true,所以创建的是CachingExecutor 实例。

调试时的部分参数值如下所示

该方法创建的 SqlSession 实例内容如下:

4.2.3DefaultSqlSession

selectOne:70, DefaultSqlSession (org.apache.ibatis.session.defaults)

@Override
  public <T> T selectOne(String statement) {
    return this.selectOne(statement, null);
  }

上述方法将调用自身的另外一个 selectOne 方法,具体见如下方法。

selectOne:76, DefaultSqlSession (org.apache.ibatis.session.defaults)

@Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.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;
    }
  }

上述方法最核心的是调用一个 selectList 方法并对查询出的结果进行大小判断,具体见如下方法。

selectList:146, DefaultSqlSession (org.apache.ibatis.session.defaults)

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

上述方法首先利用 Configuration 对象获取 MappedStatement 对象,然后将其作为参数,在 4.2.2 中已经将 Executor 对象确定下来,即 CachingExecutor 对象。因此,将调用 CachingExecutor 对象的 query 方法。

4.2.4CachingExecutor

query:81, CachingExecutor (org.apache.ibatis.executor)

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

调试过程中得到的 boundSql 和 key 内容如下图所示:

query 方法具体见如下方法。

query:95, CachingExecutor (org.apache.ibatis.executor)

@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) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

上述方法 ms.getCache() 得到的对象为 null,所以走最底下的 delegate.query 方法,具体见 4.2.5 的方法,其中的 delegate 对象是经过包装的 BaseExecutor 对象。

public class CachingExecutor implements Executor {

  private final Executor delegate;
  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }
}
4.2.5BaseExecutor

query:142, BaseExecutor (org.apache.ibatis.executor)

@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

上述方法的主要步骤有:

  • 根据 MappedStatement 对象进行 ErrorContext 的实例化
  • Executor 是否关闭判断
  • 是否请求刷新缓存判断
  • 根据 resultHandler 对象进行list的赋值
  • 根据 list 是否为 null 执行不同的获取 list 的分支

list = resultHandler == null ? (List) localCache.getObject(key) : null; 这句表示 resultHandler 不为空的话,先去查缓存并赋值给 list,否则 list 设置为 null。调试过程中,由于 list 为 null,走 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); 这个分支,具体见如下方法。

queryFromDatabase:322, BaseExecutor (org.apache.ibatis.executor)

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    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;
  }

上述方法的主要步骤有:

  • 首先往本地缓存放入一个键值对,key 是在 4.2.4 中调用 createCacheKey 方法创建的,value 暂时先用一个占位符作为值。
  • 调用 doQuery 方法查询得到 list,具体见 4.2.6 的方法
  • 删除之前创建的 value 为占位符的的键值对
  • 将查询得到的 list 作为 value 重新创建一个键值对并放入本地缓存
  • 存储过程判断及处理
  • 返回 list
4.2.6SimpleExecutor

doQuery:58, SimpleExecutor (org.apache.ibatis.executor)

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

上述方法的主要步骤有:

  • 根据 MappedStatement 对象创建 Configuration 对象
  • 调用 Configuration 对象的 newStatementHandler 方法创建 StatementHandler 对象
  • 调用 prepareStatement 方法创建 Statement 对象,具体见下方代码
  • 调用 StatementHandler 对象的 query 方法进行查询,具体见 4.2.7 的方法,入参是上述创建的 Statement 对象以及 StatementHandler 对象
  • 关闭 Statement 对象

调试过程中的部分对象内容如下所示:

prepareStatement:86, SimpleExecutor (org.apache.ibatis.executor)

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

调试过程中可以看到上述方法创建的是一个 JDBCConnection

4.2.7RoutingStatementHandler

query:79, RoutingStatementHandler (org.apache.ibatis.executor.statement)

@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    return delegate.query(statement, resultHandler);
  }

上述方法将调用 delegate,也就是 StatementHandler 对象,执行 query 方法,具体方法见4.2.8。StatementHandler 的类型是根据 MappedStatement 对象的 statementType 进行判断,调试过程中的类型为 PREPARED ,所以是 delegate 此时为一个 PreparedStatementHandler 对象,这种对象可以防止 SQL 注入,是预编译的 SQL 语句对象。

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

  }
4.2.8PreparedStatementHandler

query:63, PreparedStatementHandler (org.apache.ibatis.executor.statement)

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

上述方法调用 PreparedStatement 对象的 execute 方法,执行该对象的 SQL 语句并返回处理过的查询结果。处理 RESULT SETS 方法如下所示。

4.2.9DefaultResultSetHandler

handleResultSets:182, DefaultResultSetHandler (org.apache.ibatis.executor.resultset)

@Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

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

调试过程中,while 条件满足,进入 while 循环,由于 mappedStatement.getResultSets() 方法得到的 resultSets 为 null,因此执行最底下的collapseSingleResultList(multipleResults)方法,具体如下。

collapseSingleResultList:315, DefaultResultSetHandler (org.apache.ibatis.executor.resultset)

private List<Object> collapseSingleResultList(List<Object> multipleResults) {
    return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
  }

由于 multipleResults.size() 为1,获取首个元素并返回。

collapseSingleResultList这个方法执行完之后,开始一步一步往前返回结果

4.3 执行流程从右往左

4.3.1BaseExecutor

queryFromDatabase:326, BaseExecutor (org.apache.ibatis.executor)

# 先删除原来的本地缓存key对应的值
localCache.removeObject(key);
# 重新设置本地缓存的键值对
localCache.putObject(key, list);

query:159, BaseExecutor (org.apache.ibatis.executor)

# 查询栈自减,变成0
} finally {
      queryStack--;
    }
4.3.2DefaultSqlSession

selectList:151, DefaultSqlSession (org.apache.ibatis.session.defaults)

# 重置异常内容
} finally {
      ErrorContext.instance().reset();
    }

上述方法重置错误内容。

selectOne:77, DefaultSqlSession (org.apache.ibatis.session.defaults)

@Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.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;
    }
  }

由于 selectOne() 方法理论上应该要返回一条记录的,如果查出多条,将报 Expected one result (or null) to be returned by selectOne(), but found: xxx 异常。由于本次测试只查出一条,成功返回。

4.3.3MyBatisTest

test:43, MyBatisTest(org.apache.ibatis.submitted.call_setters_on_nulls_again)

@Test
void test() {
  try (SqlSession session = sqlSessionFactory.openSession()) {
    ParentBean parentBean = session.selectOne("test");
    Assertions.assertEquals("p1", parentBean.getName());
  }
}

至此,mybatis 查询的源码执行流程就走完了,parentBean.getName() 得到的值为 p1,测试通过。

日常求赞

最后欢迎关注我的公众号「 Java大家族 」。