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 对象。
相关推荐
大数据魔法师6 分钟前
Redis(三) - 使用Java操作Redis详解
java·数据库·redis
天天爱吃肉821816 分钟前
车载以太网驱动智能化:域控架构设计与开发实践
java·运维·网络协议·微服务
IT光17 分钟前
Redis 五种类型基础操作(redis-cli + Spring Data Redis)
java·数据库·redis·spring·缓存
keke1017 分钟前
Java【14_3】接口(Comparable和Comparator)、内部类-示例
java·开发语言·servlet
言之。35 分钟前
Go 语言中接口类型转换为具体类型
开发语言·后端·golang
代码不停43 分钟前
Java二叉树题目练习
java·开发语言·数据结构
MaCa .BaKa1 小时前
38-日语学习小程序
java·vue.js·spring boot·学习·mysql·小程序·maven
贺函不是涵1 小时前
【沉浸式求职学习day41】【Servlet】
java·学习·servlet·maven
Excuse_lighttime1 小时前
JVM 机制
java·linux·jvm
渴望技术的猿1 小时前
Windows 本地部署MinerU详细教程
java·windows·python·mineru