MyBatis SQL执行流程详解

0 阅读10分钟

本文将以最常用的select list查询流程为核心,结合核心对象、代码实现与执行链路,全面拆解MyBatis SQL执行的完整过程。

一、select list流程

1. 基本使用(编程式调用示例)

以下代码展示了MyBatis编程式调用Mapper接口执行查询列表的完整示例,涵盖从环境配置、SqlSession创建到结果获取的全步骤,适用于理解底层执行逻辑。


// 1. 获取数据源(模拟配置,实际可通过mybatis-config.xml配置)
DataSource dataSource = TestDataSourceFactory.getDataSource();
// 2. 创建事务工厂(JDBC事务工厂,依赖JDBC原生事务管理)
TransactionFactory transactionFactory = new JdbcTransactionFactory();
// 3. 构建环境对象(包含环境名称、事务工厂、数据源)
Environment environment = new Environment("dev", transactionFactory, dataSource);
// 4. 初始化配置对象(MyBatis核心配置载体,存储所有配置信息)
Configuration configuration = new Configuration(environment);
// 5. 注册Mapper接口(告知MyBatis需要扫描的Mapper,绑定SQL映射)
configuration.addMapper(TestMapper.class);
// 6. 构建SqlSessionFactory(会话工厂,线程安全,全局单例)
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
// 7. 打开SqlSession(会话对象,非线程安全,单次请求/事务独占)
SqlSession session = sqlSessionFactory.openSession();
try { 
  // 8. 获取Mapper代理对象(MyBatis动态生成,非真实Mapper实现类)
  TestMapper mapper = session.getMapper(TestMapper.class);
  // 9. 调用Mapper方法执行查询(触发底层SQL执行流程)
  List<Entity> entitys = mapper.selectList(101);
} finally {
  // 10. 关闭SqlSession(释放资源,避免连接泄漏)
  session.close();
}

2. 代码执行流程(完整链路拆解)

上述代码中,mapper.selectList(101)是触发SQL执行的入口,底层通过多组件协作完成查询,具体流程可通过以下流程图及步骤解析理解:


graph TD
A(获取mapper代理对象Configuration.getMapper) --> B(调用代理对象方法MapperMethod.execute)
B --> C(查询SqlSession.selectList)
C --> D(查询Executor.query)
D --> E(获取StatementHandlerConfiguration.newStatementHandler)
E --> F(调用插件返回StatementHandler代理interceptorChain.pluginAll)
F --> G(设置参数ParameterHandler.setParameters)
G --> H(执行sqlStatementHandler.query)
H --> I(处理结果ResultSetHandler.handleResultSets)
    

流程步骤详细解析:

  1. 获取Mapper代理对象:调用Configuration.getMapper时,MyBatis通过MapperRegistry扫描已注册的Mapper接口,利用JDK动态代理生成代理对象,代理对象会拦截所有Mapper方法调用,将请求转发至MapperMethod
  2. 方法执行分发MapperMethod.execute作为核心分发方法,会先解析当前SQL的类型(SELECT/INSERT/UPDATE/DELETE),再根据类型调用SqlSession对应的方法(如查询调用selectList),同时处理参数映射(将方法参数转换为SQL可用参数)。
  3. SqlSession层转发SqlSession作为应用与MyBatis底层的桥梁,不直接执行SQL,而是将请求委托给Executor(执行器),同时维护事务状态。
  4. Executor执行调度Executor是SQL执行的核心调度器,负责事务控制、缓存管理及SQL执行协调。调用Executor.query时,会先检查一级缓存,缓存未命中则继续向下执行,同时创建Transaction对象管理事务。
  5. 创建StatementHandler:通过Configuration.newStatementHandler创建StatementHandler,该组件是JDBC Statement的封装者,负责Statement的创建、参数设置、SQL执行及结果处理的统筹。
  6. 插件拦截增强interceptorChain.pluginAll会遍历所有注册的拦截器,对StatementHandler进行代理增强(如分页插件、数据脱敏插件可在此处拦截,修改SQL或处理参数/结果),这是MyBatis扩展的核心入口。
  7. 参数设置:通过ParameterHandler(参数处理器)将Mapper方法参数绑定到JDBC PreparedStatement的占位符上,支持多种参数类型(基本类型、对象、集合)及参数映射规则(如@Param注解、对象属性映射)。
  8. SQL执行StatementHandler.query调用JDBC StatementexecuteQuery方法执行SQL,获取数据库返回的ResultSet
  9. 结果处理ResultSetHandler(结果处理器)将ResultSet数据映射为Java对象(支持简单类型、复杂对象、集合、嵌套结果集等),最终返回给上层调用者。

二、核心对象关系及作用详解

MyBatis SQL执行流程依赖多个核心对象的协作,各对象职责清晰、相互依赖,构成MyBatis的核心架构,以下结合对象功能、实现类及关系展开说明:

1. Configuration(全局配置中心)

MyBatis的“大脑”,存储所有配置信息(全局配置、Mapper映射、插件、类型别名等),同时作为所有核心对象(Executor、StatementHandler、ParameterHandler等)的工厂,负责对象的创建与初始化,贯穿SQL执行全流程。

2. Executor(执行器,SQL执行调度核心)

负责SQL执行的统筹调度,同时处理事务控制、一级缓存管理,其实现类对应不同的执行策略,可通过配置指定默认执行器类型:

  • SimpleExecutor(默认执行器):简单执行策略,无任何优化,每执行一条SQL都会新建一个Statement对象,执行完成后关闭,适用于大多数简单场景。
  • ReuseExecutor(复用执行器):对相同SQL(SQL语句+参数配置一致)的Statement进行复用,避免频繁创建/关闭Statement,提升性能,适用于重复执行相同SQL的场景。
  • BatchExecutor(批处理执行器):实现SQL批处理功能,将多条INSERT/UPDATE/DELETE语句批量提交,减少数据库交互次数,大幅提升批处理效率,仅适用于批处理场景。
  • CachingExecutor(缓存执行器):若开启二级缓存,MyBatis会自动为上述执行器包装一层CachingExecutor,负责二级缓存的管理与查询,优先从二级缓存获取数据。

3. StatementHandler(Statement封装者)

直接与JDBC Statement交互,负责Statement的创建、参数设置、SQL执行及结果回调,是MyBatis封装JDBC操作的核心组件,默认实现类对应不同的Statement类型:

  • SimpleStatementHandler:处理普通Statement(无占位符的SQL),不支持参数绑定,适用于静态SQL场景。
  • PreparedStatementHandler(默认实现):处理PreparedStatement(带占位符的SQL),支持参数绑定、SQL预编译,可防止SQL注入,适用于大多数动态SQL场景。
  • CallableStatementHandler:处理CallableStatement,用于执行数据库存储过程,支持输入/输出参数映射。

补充:RoutingStatementHandler并非直接实现类,而是一个路由处理器,会根据SQL类型自动选择对应的StatementHandler实现类。

4. SqlSession(会话对象)

应用程序与MyBatis底层交互的入口,封装了Executor、事务等核心组件,提供增删改查方法及事务控制(提交/回滚)。默认实现为DefaultSqlSession非线程安全,需保证单次请求/事务对应一个SqlSession,使用后及时关闭。

5. InterceptorChain(拦截器链)

MyBatis的扩展机制核心,管理所有注册的拦截器(Interceptor),在核心对象创建时(如Executor、StatementHandler),通过pluginAll方法为对象生成代理,允许开发者在SQL执行的关键节点(参数设置、SQL执行、结果处理)插入自定义逻辑,常见应用场景包括自动分页、数据脱敏、日志打印、分库分表等。

6. 其他辅助组件

  • ParameterHandler:参数处理器,负责将Mapper方法参数绑定到Statement占位符,支持类型转换(如Java类型与数据库类型映射)。
  • ResultSetHandler:结果处理器,负责将ResultSet转换为Java对象,支持复杂结果映射(如一对一、一对多嵌套映射)。
  • MetaObject:元对象工具,用于操作Java对象的属性(即使属性为private),支撑参数绑定与结果映射的底层反射操作。

三、核心代码逻辑简述

以下通过核心类的关键代码,进一步理解MyBatis对象创建与SQL执行的底层逻辑,重点解析Configuration(对象工厂)与MapperMethod(方法分发)的核心实现。

1. org.apache.ibatis.session.Configuration(核心对象工厂)

该类不仅存储配置,更通过一系列newXXX方法创建核心对象,同时集成拦截器链,为对象提供增强能力,关键代码解析如下:


public class Configuration {
   /**
    * 获取Mapper代理对象
    * 核心逻辑:委托MapperRegistry创建代理,将SqlSession传入代理,便于后续调用
    */
   public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
	    return mapperRegistry.getMapper(type, sqlSession);
	}

	/**
	 * 创建元对象,用于反射操作对象属性
	 * 支撑ParameterHandler、ResultSetHandler的底层属性访问
	 */
	public MetaObject newMetaObject(Object object) {
	    return MetaObject.forObject(object, objectFactory, objectWrapperFactory);
	}

	/**
	 * 创建ParameterHandler(参数处理器)
	 * 先通过语言驱动创建默认实现,再通过拦截器链增强
	 */
	public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
	    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
        // 拦截器增强:允许插件修改参数处理逻辑
	    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
	    return parameterHandler;
	}

	/**
	 * 创建ResultSetHandler(结果处理器)
	 * 实例化默认实现DefaultResultSetHandler,再通过拦截器链增强
	 */
	public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
	      ResultHandler resultHandler, BoundSql boundSql) {
	    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
        // 拦截器增强:允许插件修改结果处理逻辑(如数据脱敏)
	    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
	    return resultSetHandler;
	}

	/**
	 * 创建StatementHandler(Statement封装者)
	 * 实例化路由处理器RoutingStatementHandler,再通过拦截器链增强
	 */
	public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
	    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        // 拦截器增强:允许插件修改SQL执行逻辑(如分页插件)
	    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
	    return statementHandler;
	}

	/**
	 * 创建Executor(执行器)
	 * 根据执行器类型实例化对应实现,开启缓存则包装为CachingExecutor,最后通过拦截器增强
	 */
	public Executor newExecutor(Transaction transaction) {
	    return newExecutor(transaction, defaultExecutorType);
	}

	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);
	    }
	    // 若开启二级缓存,包装为CachingExecutor
	    if (cacheEnabled) {
	      executor = new CachingExecutor(executor);
	    }
        // 拦截器增强:允许插件修改执行器逻辑(如事务增强、缓存扩展)
	    executor = (Executor) interceptorChain.pluginAll(executor);
	    return executor;
	}
}

2. org.apache.ibatis.binding.MapperMethod(方法执行分发器)

该类负责解析Mapper方法的SQL类型、参数信息,将方法调用分发至SqlSession对应的方法,是Mapper代理对象与SqlSession之间的桥梁,核心execute方法逻辑如下(补充完整核心逻辑):


public class MapperMethod {
  // 存储SQL命令信息(类型、语句ID等)
  private final SqlCommand command;
  // 存储方法签名信息(返回值类型、参数类型等)
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

  // 核心方法:处理增删改查方法执行,分发至SqlSession
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    // 根据SQL命令类型分发执行
    switch (command.getType()) {
      case INSERT: {
        // 处理参数映射,将args转换为SQL参数
        Object param = method.convertArgsToSqlCommandParam(args);
        // 调用SqlSession.insert,返回影响行数
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        // 无返回值且有ResultHandler参数(回调处理结果)
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        }
        // 返回列表/数组
        else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        }
        // 返回Map
        else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        }
        // 返回Cursor(游标,适用于大量数据流式查询)
        else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        }
        // 返回单个对象
        else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    // 处理返回值为null且方法返回类型为基本类型的情况(抛出异常)
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

  // 执行查询列表逻辑
  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    // 若方法有RowBounds参数(分页参数),调用带分页的selectList
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
      // 无分页,调用普通selectList
      result = sqlSession.selectList(command.getName(), param);
    }
    // 若返回值需要转换为数组,进行类型转换
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      return method.convertResultToList(result);
    }
    return result;
  }

  // 其他辅助方法(executeWithResultHandler、executeForMap等)省略...

  // 处理影响行数返回结果(适配boolean/int等返回类型)
  private Object rowCountResult(int rowCount) {
    final Object result;
    if (method.returnsVoid()) {
      result = null;
    } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
      result = rowCount;
    } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
      result = (long) rowCount;
    } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
      result = rowCount > 0;
    } else {
      throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
    }
    return result;
  }
}

四、总结

MyBatis SQL执行流程本质是“分层协作+接口抽象”的设计模式:通过Configuration统一管理配置与对象创建,SqlSession提供上层入口,Executor调度核心流程,StatementHandler封装JDBC操作,配合ParameterHandler、ResultSetHandler完成参数与结果的映射,最终通过拦截器链提供灵活扩展能力。

理解这一流程,既能帮助开发者快速定位SQL执行过程中的问题(如参数绑定错误、结果映射异常、插件拦截失效等),也能为自定义扩展(如开发插件、优化执行逻辑)提供底层支撑,真正掌握MyBatis的核心原理。