MyBatis体系结构与工作原理 上篇

课程内容

昨天内容

ORM 演化

为什么要学源码?

提高技术能力 解决工作中的问题

环境搭建




体系结构

接口层面向开发者

mybatis-》核心处理层

基础支持层 核心

MyBatis 执行 SQL 时,Mapper 代理调用 SqlSession,SqlSession 委托 Executor,Executor 经缓存与插件处理后交给 StatementHandler 构建并执行 JDBC 语句,ParameterHandler 完成参数映射,ResultSetHandler 将结果映射为对象并返回。

今天重点 -核心处理层

源码结构

源码学习

测试代码

test2

复制代码
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(in);
SqlSessionFactory

SqlSessionFactory 作用

SqlSessionFactory 是 MyBatis 的"会话工厂",负责根据全局配置创建 SqlSession,并统一管理 MyBatis 的运行环境。

build方法

全局配置解析 映射文件解析 XMLConfigBuilder

复制代码
复制代码
///别名注册
​
public Configuration() {
​
 // 为类型注册别名
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
​
    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
}
复制代码
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

XML 解析时如何通过 alias 实例化组件

复制代码
 public class TypeAliasRegistry类 {
  public TypeAliasRegistry() {
    registerAlias("string", String.class);
​
    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
  private final Map<String, Class<?>> typeAliases = new HashMap<>();
​
public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748
    String key = alias.toLowerCase(Locale.ENGLISH);
    if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
    }
    typeAliases.put(key, value);
  }
复制代码
private final Map<String, Class<?>> typeAliases = new HashMap<>();

默认值的初始化

反射组件不知道哪个适合启用了

复制代码
private void parseConfiguration(XNode root) {

继续分析

复制代码
return build(parser.parse());
复制代码
 build(Configuration config)

(parser.parse() 解析了Configuration

root包含了整个配置文件

复制代码
​
  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      // ===== MyBatis 全局配置文件(mybatis-config.xml)解析主流程 =====
​
// 1. 解析 <properties>:加载外部属性文件/变量(用于占位符 ${} 替换)  <properties resource="db.properties"/>
      propertiesElement(root.evalNode("properties"));
​
// 2. 解析 <settings>:读取框架核心开关配置(如缓存、懒加载、驼峰映射等)
      Properties settings = settingsAsProperties(root.evalNode("settings"));
​
// 2.1 根据 settings 加载自定义 VFS 实现(用于资源扫描,如 Jar/OSGi 等)
      loadCustomVfs(settings);
​
// 2.2 根据 settings 设置日志实现(SLF4J、LOG4J、STDOUT 等)
      loadCustomLogImpl(settings);
​
// 3. 解析 <typeAliases>:注册类型别名(简化 XML 中类名配置)
      typeAliasesElement(root.evalNode("typeAliases"));
​
// 4. 解析 <plugins>:加载 MyBatis 插件体系(Interceptor 拦截 Executor/StatementHandler 等)
      pluginElement(root.evalNode("plugins"));
​
// 5. 解析 <objectFactory>:指定对象创建工厂(控制结果对象实例化方式)
      objectFactoryElement(root.evalNode("objectFactory"));
​
// 6. 解析 <objectWrapperFactory>:对象包装工厂(用于增强对象属性访问,如 Map/Collection)
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
​
// 7. 解析 <reflectorFactory>:反射缓存工厂(提升反射性能,管理 Reflector)
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
​
// 8. 应用 <settings> 到 Configuration:将 settings 值真正赋给全局配置对象(默认值也在此补全)
      settingsElement(settings);
​
// 注意:settings 必须在 objectFactory/objectWrapperFactory 初始化后设置(历史 issue #631)
​
// 9. 解析 <environments>:构建运行环境(事务管理器 + 数据源 DataSource)
//    → 最终决定 Connection 从哪里来、事务如何控制
      environmentsElement(root.evalNode("environments"));
​
// 10. 解析 <databaseIdProvider>:数据库厂商识别(用于多数据库 SQL 区分,如 mysql/oracle)
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
​
// 11. 解析 <typeHandlers>:注册类型转换器(Java 类型 ↔ JDBC 类型映射)
      typeHandlerElement(root.evalNode("typeHandlers"));
​
// 12. 解析 <mappers>:加载 Mapper 映射器(XML/注解)
//     → 生成 MappedStatement,建立 statementId → SQL 的映射关系
      mapperElement(root.evalNode("mappers"));
​
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

信息保存

复制代码
// 12. 解析 <mappers>:加载 Mapper 映射器(XML/注解)
//     → 生成 MappedStatement,建立 statementId → SQL 的映射关系
      mapperElement(root.evalNode("mappers"));
复制代码
复制代码
XMLStatementBuilder
Mapper XML 中每一个 <select> 节点都会对应一个 XMLStatementBuilder 解析器,它基于 XNode 节点内容,在 MapperBuilderAssistant 辅助下生成 MappedStatement,并注册到全局 Configuration 中,供后续 SqlSession 执行时查找调用。
对象/字段 类型 是否"每个SQL节点一个" 核心作用 类比理解
statementParser XMLStatementBuilder ✅ 是 专门解析某一个 <select>/<insert> 节点,最终生成 MappedStatement 单条SQL的"编译器"
builderAssistant MapperBuilderAssistant ❌ 一个Mapper文件共享 提供命名空间、辅助注册 statement/resultMap 等 Mapper构建助手
context XNode ✅ 是 当前 XML 节点对象(保存SQL标签内容和属性) DOM节点包装
requiredDatabaseId String ❌ 可选 多数据库适配用,判断是否加载该SQL 数据库过滤器
configuration Configuration ❌ 全局唯一 MyBatis运行期核心容器,存放所有Mapper、SQL、插件等 全局注册中心
typeAliasRegistry TypeAliasRegistry ❌ 全局唯一 类型别名管理(XML中短名称→Class) 类名字典
typeHandlerRegistry TypeHandlerRegistry ❌ 全局唯一 Java类型↔JDBC类型转换器注册表 类型转换中心

一个标签的转化 增删改查的标签

复制代码
statementParser.parseStatementNode();

xmlstatemxml

复制代码
 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
复制代码
​
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);
​
​
复制代码
 // 最关键的一步,在 Configuration 添加了 MappedStatement >>
    configuration.addMappedStatement(statement);
    return statement;
复制代码
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());

statement 对应的crud标签

复制代码
public class DefaultSqlSessionFactory implements SqlSessionFactory {

时序图

会话对象

DefaultSqlSession

它是应用层与 MyBatis 执行器(Executor)之间的桥梁,负责SQL调用的统一入口与事务管理。

复制代码
  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
​

configuration.getDefaultExecutorType()

复制代码
  public ExecutorType getDefaultExecutorType() {
    return defaultExecutorType;
  }
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
 * @author Clinton Begin
 */
public enum ExecutorType {
  SIMPLE, REUSE, BATCH
}
​

执行器作用

ExecutorType 执行器机制 是否复用 Statement 是否批量执行 典型使用场景 优点 注意点
SIMPLE 每次执行创建新的 Statement 默认 CRUD、普通查询 行为最直观、安全 性能一般,频繁创建 Statement
REUSE 复用已创建的 Statement 同一 SQL 多次执行 减少 Statement 创建开销 只复用 Statement,不合并 SQL
BATCH 批量缓存 SQL,统一提交 是(批量) 批量 insert / update 大幅提升批量写性能 需手动 flushStatements()

ExecutorType 决定 MyBatis SQL 的执行模型defaultExecutorType 决定 全局默认行为

复制代码
public final class Environment {
  private final String id;
  private final TransactionFactory transactionFactory;
  private final DataSource dataSource;
    
 

Environment 类在 MyBatis 中是非常核心的配置对象,它主要用来封装一个运行环境下 数据库连接和事务相关的配置

复制代码
 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();
    }
  }

final Executor executor = configuration.newExecutor(tx, execType); 执行sql语句的执行器

Rule

复制代码
 // 缓存 Statement
private final Map<String, Statement> statementMap = new HashMap<>();
​
public ReuseExecutor(Configuration configuration, Transaction transaction) {
  super(configuration, transaction);
}
复制代码
executor = new ReuseExecutor(this, transaction);// 缓存
​

不同的策略对应不同的选择,例如缓存,就可以直接从缓存中拿

复制代码
  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }
select代理创建

sql语句的执行

getMapper

复制代码
  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
复制代码
public T newInstance(SqlSession sqlSession) {
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}
复制代码
  protected T newInstance(MapperProxy<T> mapperProxy) {
    // 1:类加载器:2:被代理类实现的接口、3:实现了 InvocationHandler 的触发管理类
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
复制代码
public class MapperProxy<T> implements InvocationHandler, Serializable {
​
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // toString hashCode equals getClass等方法,无需走到执行SQL的流程
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        // 提升获取 mapperMethod 的效率,到 MapperMethodInvoker(内部接口) 的 invoke
        // 普通方法会走到 PlainMethodInvoker(内部类) 的 invoke
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }
代理执行

代理对象的MapperProxy

复制代码
public class MapperProxy<T> implements InvocationHandler, Serializable {
​
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // toString hashCode equals getClass等方法,无需走到执行SQL的流程
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        // 提升获取 mapperMethod 的效率,到 MapperMethodInvoker(内部接口) 的 invoke
        // 普通方法会走到 PlainMethodInvoker(内部类) 的 invoke
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }
复制代码
/// 返回的MapperMethodInvoker 这个实例
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
复制代码
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
  return mapperMethod.execute(sqlSession, args);
}
复制代码
  private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;
​
    public PlainMethodInvoker(MapperMethod mapperMethod) {
      super();
      this.mapperMethod = mapperMethod;
    }
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      // SQL执行的真正起点
      return mapperMethod.execute(sqlSession, args);
    }
  }
sql执行
复制代码
  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
    //拿取指之前初始化的MappedStatement
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 如果 cacheEnabled = true(默认),Executor会被 CachingExecutor装饰
      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();
    }
  }
复制代码
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    // cache 对象是在哪里创建的?  XMLMapperBuilder类 xmlconfigurationElement()
    // 由 <cache> 标签决定
    if (cache != null) {
      // flushCache="true" 清空一级二级缓存 >>
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        // 获取二级缓存
        // 缓存通过 TransactionalCacheManager、TransactionalCache 管理
        @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;
      }
    }
    // 走到 SimpleExecutor | ReuseExecutor | BatchExecutor
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

增强

复制代码
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
​
  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);
    // 植入插件逻辑(返回代理对象)
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }
​
  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // StatementType 是怎么来的? 增删改查标签中的 statementType="PREPARED",默认值 PREPARED
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        // 创建 StatementHandler 的时候做了什么? >>
        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());
    }
​
  }
复制代码
stmt = prepareStatement(handler, ms.getStatementLog());
复制代码
//获取链接
protected Connection getConnection(Log statementLog) throws SQLException {
  Connection connection = transaction.getConnection();
  if (statementLog.isDebugEnabled()) {
    return ConnectionLogger.newInstance(connection, statementLog, queryStack);
  } else {
    return connection;
  }
}
复制代码
//获取Statement
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);
      setStatementTimeout(statement, transactionTimeout);
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }
层次 对象 关系
JDBC Statement / PreparedStatement 一条 SQL 对应一个 Statement
MyBatis SqlSession 可以执行多条 SQL,一个 SqlSession 可以生成多个 Statement
MyBatis Executor 每条 SQL 会通过 Executor 调用 StatementHandler 生成 Statement
返回处理
复制代码
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
  }
复制代码
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
​
    // 设置当前错误上下文:正在处理结果集,并绑定当前 MappedStatement 的 id
    ErrorContext.instance()
            .activity("handling results")
            .object(mappedStatement.getId());
​
    // 用于存放所有结果集解析后的结果(可能是多个 ResultSet)
    final List<Object> multipleResults = new ArrayList<>();
​
    // 当前处理到第几个 ResultSet(从 0 开始)
    int resultSetCount = 0;
​
    // 获取第一个 ResultSet,并包装成 ResultSetWrapper(便于统一处理元数据)
    ResultSetWrapper rsw = getFirstResultSet(stmt);
​
    // 从 MappedStatement 中获取所有配置的 ResultMap
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
​
    // ResultMap 的数量
    int resultMapCount = resultMaps.size();
​
    // 校验 ResultSet 与 ResultMap 的数量是否匹配(防止配置错误)
    validateResultMapsCount(rsw, resultMapCount);
​
    /**
     * 第一阶段:
     * 处理“主结果集”
     * - 一个 ResultSet 对应一个 ResultMap
     * - 常见于普通查询或多结果集返回
     */
    while (rsw != null && resultMapCount > resultSetCount) {
​
        // 根据当前索引获取对应的 ResultMap
        ResultMap resultMap = resultMaps.get(resultSetCount);
​
        // 处理当前 ResultSet:
        // - rsw:当前结果集
        // - resultMap:映射规则
        // - multipleResults:将结果加入到该集合中
        // - null:此时不是嵌套结果映射
        handleResultSet(rsw, resultMap, multipleResults, null);
​
        // 获取下一个 ResultSet(Statement 可能返回多个)
        rsw = getNextResultSet(stmt);
​
        // 清理当前 ResultSet 处理过程中产生的临时状态
        cleanUpAfterHandlingResultSet();
​
        // 结果集索引递增
        resultSetCount++;
    }
​
    /**
     * 第二阶段:
     * 处理“嵌套结果集”(多 ResultSet 关联)
     * - 通常用于存储过程
     * - 通过 resultSets 属性指定 ResultSet 名称
     * - 与父结果集进行关联映射
     */
    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
​
        // 继续遍历剩余的 ResultSet
        while (rsw != null && resultSetCount < resultSets.length) {
​
            // 根据 resultSets 中的名称,找到对应的父 ResultMapping
            ResultMapping parentMapping =
                    nextResultMaps.get(resultSets[resultSetCount]);
​
            if (parentMapping != null) {
​
                // 获取嵌套 ResultMap 的 id
                String nestedResultMapId =
                        parentMapping.getNestedResultMapId();
​
                // 从 Configuration 中获取嵌套 ResultMap
                ResultMap resultMap =
                        configuration.getResultMap(nestedResultMapId);
​
                // 处理嵌套 ResultSet:
                // - multipleResults 为 null(不作为独立结果返回)
                // - parentMapping 用于将结果挂接到父对象
                handleResultSet(rsw, resultMap, null, parentMapping);
            }
​
            // 获取下一个 ResultSet
            rsw = getNextResultSet(stmt);
​
            // 清理临时状态
            cleanUpAfterHandlingResultSet();
​
            // 索引递增
            resultSetCount++;
        }
    }
​
    /**
     * 如果最终只有一个结果集:
     * - 返回该结果本身(而不是 List<List<>>)
     * 如果有多个结果集:
     * - 返回 List<Object>
     */
    return collapseSingleResultList(multipleResults);
}
​
复制代码
/**
 * 处理单个 ResultSet,并根据是否为嵌套映射决定结果的去向
 *
 * @param rsw             ResultSet 的包装对象,包含结果集及其元数据
 * @param resultMap       当前 ResultSet 对应的 ResultMap 映射规则
 * @param multipleResults 用于存放“主结果集”的结果列表(嵌套结果时为 null)
 * @param parentMapping   父级 ResultMapping(不为 null 表示这是嵌套结果集)
 */
private void handleResultSet(ResultSetWrapper rsw,
                             ResultMap resultMap,
                             List<Object> multipleResults,
                             ResultMapping parentMapping) throws SQLException {
  try {
​
    /**
     * 场景一:嵌套结果集(多 ResultSet 关联)
     * - parentMapping 不为 null
     * - 当前 ResultSet 的数据需要“挂接”到父结果对象上
     * - 不作为独立查询结果返回
     */
    if (parentMapping != null) {
​
      // 处理行数据:
      // - resultHandler 为 null(不需要收集成独立结果)
      // - RowBounds.DEFAULT:不进行分页
      // - parentMapping:用于父子对象关联
      handleRowValues(
              rsw,
              resultMap,
              null,
              RowBounds.DEFAULT,
              parentMapping
      );
​
    } else {
​
      /**
       * 场景二:普通结果集(非嵌套)
       */
​
      // 如果用户没有自定义 ResultHandler
      if (resultHandler == null) {
​
        // 使用默认的结果处理器
        DefaultResultHandler defaultResultHandler =
                new DefaultResultHandler(objectFactory);
​
        // 处理行数据:
        // - 将每一行映射成对象
        // - 由 DefaultResultHandler 收集到内部 List 中
        handleRowValues(
                rsw,
                resultMap,
                defaultResultHandler,
                rowBounds,
                null
        );
​
        // 将当前 ResultSet 的结果(一个 List)加入 multipleResults
        multipleResults.add(
                defaultResultHandler.getResultList()
        );
​
      } else {
​
        /**
         * 场景三:用户自定义 ResultHandler
         * - MyBatis 不再收集结果
         * - 由用户自行处理每一行数据(如流式处理、大数据量)
         */
        handleRowValues(
                rsw,
                resultMap,
                resultHandler,
                rowBounds,
                null
        );
      }
    }
​
  } finally {
​
    /**
     * 无论是否发生异常,都必须关闭 ResultSet
     * issue #228:
     * - 修复 ResultSet 未关闭导致的数据库资源泄露问题
     */
    closeResultSet(rsw.getResultSet());
  }
}
​

核心工具类

核心流程

相关推荐
Re_zero3 分钟前
线上日志被清空?这段仅10行的 IO 代码里竟然藏着3个毒瘤
java·后端
洋洋技术笔记9 分钟前
Spring Boot条件注解详解
java·spring boot
程序员清风18 小时前
程序员兼职必看:靠谱软件外包平台挑选指南与避坑清单!
java·后端·面试
皮皮林55119 小时前
利用闲置 Mac 从零部署 OpenClaw 教程 !
java
华仔啊1 天前
挖到了 1 个 Java 小特性:var,用完就回不去了
java·后端
SimonKing1 天前
SpringBoot整合秘笈:让Mybatis用上Calcite,实现统一SQL查询
java·后端·程序员
日月云棠2 天前
各版本JDK对比:JDK 25 特性详解
java
用户8307196840822 天前
Spring Boot 项目中日期处理的最佳实践
java·spring boot
JavaGuide2 天前
Claude Opus 4.6 真的用不起了!我换成了国产 M2.5,实测真香!!
java·spring·ai·claude code
IT探险家2 天前
Java 基本数据类型:8 种原始类型 + 数组 + 6 个新手必踩的坑
java