1.调用链 -mybatis源码学习

发布于:2025-09-14 ⋅ 阅读:(17) ⋅ 点赞:(0)

调用链

从我们环境准备的断点开始,试着一步步 F7 查看一次 select 请求的全过程,大致的对调用链有一个印象即可

基础知识

我们引入了 Spring,因此调用链中会混有 Spring 以及用于集成 Spring 的类,它不会影响 mybatis 的逻辑,我们不深究其实现,但是区分代码边界还是有必要的。我们需要通过类的包名来简单的识别这个代码所属的模块以及其职责。

  • org.apache.ibatis

    mybatis 核心代码,我们学习的重点

  • org.mybatis.spring

    mybatis 与 spring 的集成库,用于连接 mybatis 与 spring

  • sample.mybatis

    我们引入的框架,是整个调用链的起点

  • org.springframework

    spring 框架的底层基础设施

  • java.sql

    jdbc 的实现

调用链

业务代码与 mybatis 的边界

使用 debug 模式启动 mybatis-spring-boot-sample-web,在 CityRestController 类的 getCity 方法实现上打断点

调用 http://localhost:8080/cities/CA

程序会停在断点行,可以 ctrl+鼠标左键 看一下 CityMapper.findByState 的定义,包括其方法名、参数定义、@Select 注解,同时我们可以注意到 cityMapper 是一个代理类

MapperProxy

按下 F7 步入进 MapperProxy,这一步使用了 jdk 动态代理,其方法栈的上一层 findByState:-1, $Proxy62 (com.sun.proxy) 为代理类实例。

如果对 jdk 动态代理机制不了解的话,只需要知道代理类实例会调用 MapperProxy 的 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 方法即可,其参数分别为 被代理类实例、被调用的方法的 Method 对象、调用的参数列表

MapperProxy 本身是一个有状态的实例,生命周期与其持有的 SqlSession 相同。它由 MapperProxyFactory 创建,构造函数的三个参数 sqlSession、mapperInterface、methodCache 分别是 本次分配给它的 sqlSession、被代理的 mapper 接口的 Class 对象、方法缓存。

注意到这里的 sqlSession 实例实际上是 SqlSessionTemplate。

步入进 cachedInvoker 方法,该方法负责创建并缓存方法执行器 MapperMethodInvoker 对象。methodCache 由 MapperProxyFactory 传递的引用,SqlSessionFactory 会给每一个它管理的 Mapper 接口分配一个对应的 MapperProxyFactory,所以 methodCache 是以 SqlSessionFactory 为边界,以 Mapper 接口为单位的缓存

暂不关心 MapperMethodInvoker 创建的具体实现,步退出 cachedInvoker 方法,步入 invoke 方法,可以看到此处的 invoke 是 PlainMethodInvoker,MapperMethodInvoker 这个抽象层其实只是为了处理 java8 引入的 default 方法,绝大多数方法都使用 PlainMethodInvoker 处理。它持有了一个 MapperMethod 对象,在引入 MapperMethodInvoker 抽象层之前被缓存的其实就是它

MapperMethod

步入 execute 方法,简单浏览可以看出该类负责根据需要执行的 sql 类型及返回值类型拼装参数并调用不同的 sqlSession 的方法,同样的我们不过多关心具体实现,步入进 sqlSession.selectOne 方法

mybatis-spring

此时会发现跳转的不是源码而是依赖,这个部分是mybatis 与 spring 的集成库的代码

在集成spring 时,MapperProxy 持有并传递给 MapperMethod 实例的不是一个真正的 SqlSession 实例而是 SqlSessionTemplate。

SqlSessionTemplate 用来解决原生 SqlSession 的线程安全问题,并且用来集成 Spring 的事务以及分配真正的 SqlSession 用于执行 sql。

DefaultSqlSession

我们直接在 DefaultSqlSession.selectOne 实现内打断点跳过 mybatis-spring,此时我们真正进入了 mybatis 核心实现。

连续步入 selectList 方法,可以看到中间的几个重载只是添加了默认的 RowBounds 和 ResultHandler。

  private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      // 获取这个方法对应的 MappedStatement 对象,它封装了和需要执行的 sql 相关的各种信息
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 判断这个查询会不会脏缓存
      dirty |= ms.isDirtySelect();
      // 具体的执行工作被委托给了 Executor
      return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

MappedStatement 是 MyBatis 的核心元数据,绑定了 SQL、参数映射、结果映射、缓存策略、statement 类型等,不过我们暂不关心 MappedStatement 是怎么构造及缓存的

Executor

步入 executor.query 方法,此外,注意到这是个 CachingExecutor

  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler)
      throws SQLException {
    // 根据 MappedStatement 和参数对象生成 BoundSql:
    // 具体来说它包含了
    // 1.解析动态(<if>, <foreach>等),将 #{param} 占位符转换为 JDBC 占位符 '?' 后的 sql
    // 2.参数映射列表ParameterMapping,记录每个占位符对应的参数信息,包括类型及具体值
    // 3.原始参数对象及其他上下文信息
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 结合方法签名、分页信息、BoundSql 的 SQL 语句及参数等映射生成唯一 key
    // 会作为mybatis一二级缓存的key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

接着步入 query 方法,该方法负责查询二级缓存

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

接着步入 query 方法,注意到实现在 BaseExecutor 而不是刚刚的 CachingExecutor

该方法是在尝试查询一级缓存

  // 这个部分是在处理一级缓存,逻辑很复杂,我们暂不深究
  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;
  }

步入 queryFromDatabase 方法,该方法负责保存一级缓存

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

接着步入 doQuery 方法,注意到实现来到 SimpleExecutor

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实例,它是数据库操作的直接执行者
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler,
        boundSql);
    // 准备一个 JDBC 的 Statement 对象,我们暂不深入其调用链
    stmt = prepareStatement(handler, ms.getStatementLog());
    // 执行查询并处理结果
    return handler.query(stmt, resultHandler);
  } finally {
    // 释放 JDBC 资源
    closeStatement(stmt);
  }
}

在退出这一章前我们先简单梳理一下这几个 Executor 的结构

  • CachingExecutor 是装饰器,主要处理二级缓存

  • BaseExecutor 是抽象基类,处理一级缓存和调用骨架

  • SimpleExecutor、ReuseExecutor、BatchExecutor 负责不同的 Statement 生命周期管理

StatementHandler

步入 handler.query 方法,注意到这是个 RoutingStatementHandler,delegate 属性中持有的是一个 PreparedStatementHandler。
这算是一个工厂加委托模式的实现,RoutingStatementHandler 实例创建时会根据 ms.getStatementType() 不同来创建真正的 StatementHandler 并赋值到 delegate 属性,它的方法只负责委托给持有的 delegate 实例而不直接实现 SQL 执行逻辑。

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {  
  // 将方法委托给delegate
  return delegate.query(statement, resultHandler);  
}

步入 handler.query 方法,注意到实现来到 PreparedStatementHandler,绝大多数 sql 都会使用 PreparedStatementHandler 处理

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {  
  PreparedStatement ps = (PreparedStatement) statement;  
  // 执行真正的查询,再往里就是jdbc的实现了,我们就止步于此
  ps.execute();  
  // 处理返回值,将JDBC的ResultSet转换成用户返回类型,我们暂时也不再深入了
  return resultSetHandler.handleResultSets(ps);  
}

时序图

客户端 CityRestController MapperProxy (JDK Proxy) SqlSessionTemplate CachingExecutor (L2 Cache) BaseExecutor (L1 Cache) SimpleExecutor RoutingStatementHandler PreparedStatementHandler 数据库 (JDBC) GET /cities/CA cityMapper.findByState("CA") 业务代码与MyBatis的边界 selectOne("...findByState", "CA") Spring集成层,管理事务和SqlSession query(...) 默认情况下,SqlSession持有的Executor是CachingExecutor 1. 创建 CacheKey 2. 查询二级缓存 (L2 Cache) - 未命中 delegate.query(...) 调用被装饰的下一级Executor 3. 查询一级缓存 (L1 Cache) - 未命中 doQuery(...) 调用子类实现,真正执行数据库查询 newStatementHandler(...) 创建语句处理器 构造时进行路由 prepareStatement(...) connection.prepareStatement(sql) preparedStatement.set...() query(stmt, ...) 执行查询 & 结果映射 preparedStatement.execute() 返回 ResultSet 返回 List<City> 返回查询结果 4. 将结果存入一级缓存 (L1 Cache) 返回查询结果 5. 将结果存入二级缓存暂存区 (TCM) 返回查询结果 返回查询结果 返回查询结果 (City对象) 返回 JSON 数据 客户端 CityRestController MapperProxy (JDK Proxy) SqlSessionTemplate CachingExecutor (L2 Cache) BaseExecutor (L1 Cache) SimpleExecutor RoutingStatementHandler PreparedStatementHandler 数据库 (JDBC)