mybatis源码-深入分析sql执行流程

知其然要知其所以然,探索每一个知识点背后的意义,你知道的越多,你不知道的越多,一起学习,一起进步,如果文章感觉对您有用的话,关注、收藏、点赞,有困惑的地方请评论,我们一起交流!


一、MyBatis SQL 执行流程图

sequenceDiagram participant Client participant MapperProxy participant SqlSession participant Executor participant StatementHandler participant ParameterHandler participant ResultSetHandler participant JDBC Client->>MapperProxy: 调用Mapper接口方法 MapperProxy->>SqlSession: 获取SqlSession实例 SqlSession->>Executor: 委托执行 Executor->>Executor: 处理缓存(一级/二级) Executor->>StatementHandler: 创建Statement StatementHandler->>ParameterHandler: 设置参数 ParameterHandler->>JDBC: 填充PreparedStatement参数 JDBC->>JDBC: 执行SQL JDBC->>ResultSetHandler: 返回ResultSet ResultSetHandler->>Executor: 转换为Java对象 Executor->>SqlSession: 返回结果 SqlSession->>MapperProxy: 返回结果 MapperProxy->>Client: 返回结果

二、流程详解与源码分析

1. Mapper 接口方法调用(动态代理)

  • 入口类MapperProxy(动态代理类)

  • 源码路径org.apache.ibatis.binding.MapperProxy

  • 核心逻辑

    java 复制代码
    public Object invoke(Object proxy, Method method, Object[] args) {
      // 1. 解析方法签名
      MapperMethod mapperMethod = cachedMapperMethod(method);
      // 2. 调用 SqlSession 执行 SQL
      return mapperMethod.execute(sqlSession, args);
    }

2. SqlSession 委托执行

  • 核心类DefaultSqlSession

  • 源码路径org.apache.ibatis.session.defaults.DefaultSqlSession

  • 关键方法

    java 复制代码
    public <E> List<E> selectList(String statement, Object parameter) {
      // 获取 MappedStatement(包含 SQL 定义)
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 委托给 Executor 执行
      return executor.query(ms, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
    }

3. Executor 处理缓存与执行

  • 核心类BaseExecutor(抽象基类)

  • 源码路径org.apache.ibatis.executor.BaseExecutor

  • 关键逻辑

    java 复制代码
    public <E> List<E> query(MappedStatement ms, Object parameter, ...) {
      // 1. 生成缓存 Key
      CacheKey key = createCacheKey(ms, parameter, ...);
      // 2. 检查一级缓存
      if (ms.isUseCache() && cache != null) {
        List<E> cachedList = (List<E>) cache.getObject(key);
        if (cachedList != null) return cachedList;
      }
      // 3. 未命中缓存,执行查询
      return queryFromDatabase(ms, parameter, ...);
    }
    
    private <E> List<E> queryFromDatabase(...) {
      // 调用 doQuery()(子类实现)
      List<E> list = doQuery(ms, parameter, ...);
      // 写入一级缓存
      if (ms.isUseCache()) cache.putObject(key, list);
      return list;
    }

4. StatementHandler 创建 Statement

  • 核心类PreparedStatementHandler

  • 源码路径org.apache.ibatis.executor.statement.PreparedStatementHandler

  • 关键方法

    java 复制代码
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) {
      PreparedStatement ps = (PreparedStatement) statement;
      ps.execute();
      // 处理结果集
      return resultSetHandler.handleResultSets(ps);
    }

5. ParameterHandler 参数绑定

  • 核心类DefaultParameterHandler

  • 源码路径org.apache.ibatis.scripting.defaults.DefaultParameterHandler

  • 关键逻辑

    java 复制代码
    public void setParameters(PreparedStatement ps) {
      // 遍历参数映射,逐个设置参数
      for (ParameterMapping paramMapping : parameterMappings) {
        Object value = ...; // 从参数对象中提取值
        TypeHandler typeHandler = paramMapping.getTypeHandler();
        typeHandler.setParameter(ps, i + 1, value, paramMapping.getJdbcType());
      }
    }

6. ResultSetHandler 结果映射

  • 核心类DefaultResultSetHandler

  • 源码路径org.apache.ibatis.executor.resultset.DefaultResultSetHandler

  • 关键逻辑

    java 复制代码
    public List<Object> handleResultSets(Statement stmt) {
      // 遍历 ResultSet,逐行映射为 Java 对象
      while (rsw != null && resultMapCount > resultSetIndex) {
        ResultMap resultMap = resultMaps.get(resultSetIndex);
        Object rowValue = getRowValue(rsw, resultMap);
        addToResultMap(rowValue);
      }
      return resultList;
    }

三、核心类图与交互

plaintext 复制代码
MapperProxy → SqlSession → Executor → StatementHandler → JDBC
                                  ↑           ↓
                                Cache   ResultSetHandler

四、关键设计模式

  1. 动态代理模式MapperProxy 拦截接口方法调用。
  2. 模板方法模式BaseExecutor 定义执行流程,子类实现具体逻辑。
  3. 装饰器模式CachingExecutor 包装普通 Executor 添加缓存功能。
  4. 责任链模式InterceptorChain 管理插件拦截逻辑。

五、调试技巧与实战示例

1. 断点设置

  • MapperProxy.invoke():观察方法调用如何转换为 SQL 操作。
  • PreparedStatementHandler.query():跟踪 JDBC 执行过程。
  • DefaultResultSetHandler.handleResultSets():分析结果集映射逻辑。

2. 日志输出

mybatis-config.xml 中开启 Debug 日志:

xml 复制代码
<configuration>
  <settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
  </settings>
</configuration>

3. 实战示例:跟踪 SELECT 查询

java 复制代码
// Mapper 接口
public interface UserMapper {
  @Select("SELECT * FROM users WHERE id = #{id}")
  User getUserById(int id);
}

// 调用代码
User user = sqlSession.getMapper(UserMapper.class).getUserById(1);
  • 执行路径MapperProxyDefaultSqlSession.selectOne()CachingExecutor.query()PreparedStatementHandler.query()DefaultResultSetHandler.handleResultSets().

六、高级特性扩展

1. 插件拦截 SQL 执行

  • 拦截点ExecutorStatementHandlerParameterHandlerResultSetHandler

  • 示例插件 :统计 SQL 执行时间。

    java 复制代码
    @Intercepts(@Signature(type = Executor.class, method = "query", args = {...}))
    public class SqlTimeInterceptor implements Interceptor {
      @Override
      public Object intercept(Invocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = invocation.proceed();
        System.out.println("SQL 耗时: " + (System.currentTimeMillis() - start) + "ms");
        return result;
      }
    }

2. 自定义类型处理器

  • 核心接口TypeHandler<T>

  • 示例 :处理枚举类型存储为字符串。

    java 复制代码
    public class EnumTypeHandler<E extends Enum<E>> implements TypeHandler<E> {
      @Override
      public void setParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) {
        ps.setString(i, parameter.name());
      }
      @Override
      public E getResult(ResultSet rs, String columnName) {
        String value = rs.getString(columnName);
        return Enum.valueOf(type, value);
      }
    }

七、总结

通过以上流程分析,可以清晰看到 MyBatis 如何将 Mapper 接口的方法调用转化为 JDBC 操作。核心在于:

  1. 动态代理:将接口调用映射到 SQL 执行。
  2. 执行器链 :通过 Executor 实现缓存、事务管理。
  3. 结果映射 :灵活地将 ResultSet 转为 Java 对象。
相关推荐
Baihai_IDP6 分钟前
【译】TPU Deep Dive:Google TPU 架构深度分析
人工智能·google·面试
GoGeekBaird9 分钟前
大模型应用的五大拦路虎:一位从业者的深度反思与破局指南
后端·github
洛卡卡了9 分钟前
面试官问我会不会用 AI,我拿出这个 Ollama + FastGPT 项目给他看
人工智能·后端·docker
ifanatic10 分钟前
[每周一更]-(第148期):使用 Go 进行网页抓取:Colly 与 Goquery 的对比与思路
开发语言·后端·golang
Seven9717 分钟前
剑指offer-17、树的⼦结构
java
二闹17 分钟前
大厂前端研发岗位设计的30道Webpack面试题及解析
前端·面试
一念杂记25 分钟前
【实战系列】30分钟开发微信小程序登录&注册&绑定功能
前端·后端·微信小程序
福娃B27 分钟前
【CSS】面试必会—浮动布局:让元素“漂浮”的艺术
前端·css·面试
ZzMemory30 分钟前
详解JavaScript 解构赋值:让你的代码更优雅
前端·javascript·面试
PineappleCoder31 分钟前
CSS那些你不得不懂的“潜规则”(二)
前端·css·面试