3. MyBatis Executor:SQL 执行的核心引擎

MyBatis Executor:SQL 执行的核心引擎

上一篇文章我们介绍了Mybatis是如何前置处理参数,把Mapper中可能的各种各样的参数,统一处理成Map,方便后续处理。接下来,继续深入,看下在SqlSession中,如何继续执行。

一、从SqlSession.selectOne说起

当我们通过SqlSession调用selectOne方法时,看似简单的操作背后,其实是Executor在完成实际工作。我们先从DefaultSqlSession的selectOne源码说起:

java 复制代码
@Override
public <T> T selectOne(String statement, Object parameter) {
    // 调用selectList获取结果集
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
        return list.get(0);
    } else if (list.size() > 1) {
        throw new TooManyResultsException("Expected one result but found: " + list.size());
    } else {
        return null;
    }
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
    return selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        // 获取MappedStatement(SQL元信息)
        MappedStatement ms = configuration.getMappedStatement(statement);
        // 核心:调用Executor的query方法
        return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error querying database", e);
    }
}

从源码可见,SqlSession的selectOne最终会委托给executor.query()方法。SqlSession更像是一个 "门面",而Executor才是真正负责SQL执行的核心组件。Executor接口定义了 MyBatis 执行 SQL 的标准,其实现类则根据不同场景提供了多样化的执行策略。

二、Executor 接口的实现体系

Executor是一个接口,定义了 SQL 执行的核心方法(query/update/commit/rollback等)。MyBatis 通过模板+策略模式,构建了灵活的执行器体系。下面是Executor相关的类图:

classDiagram direction TB class Executor { <> +query(ms, parameter, rowBounds, resultHandler) List +update(ms, parameter) int +commit(required) void +rollback(required) void +getTransaction() Transaction +close(forceRollback) void +clearLocalCache() void } class BaseExecutor { <> -localCache: PerpetualCache -transaction: Transaction +query(ms, parameter, rowBounds, resultHandler) List +update(ms, parameter) int +commit(required) void +rollback(required) void #prepareStatement(handler, statementLog) Statement #queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql) List } class SimpleExecutor { +prepareStatement(handler, statementLog) Statement } class ReuseExecutor { -statementMap: Map +prepareStatement(handler, statementLog) Statement } class BatchExecutor { -statementList: List -batchResults: List -currentSql: String -currentStatement: MappedStatement +doUpdate(ms, parameter) int +doFlushStatements(isRollback) List~BatchResult~ } Executor <|-- BaseExecutor BaseExecutor <|-- SimpleExecutor BaseExecutor <|-- ReuseExecutor BaseExecutor <|-- BatchExecutor

1. BaseExecutor:Executor的基类

BaseExecutorSimpleExecutorReuseExecutor等具体执行器的抽象父类,主要实现了Mybatis的一级缓存,也就是SqlSession级别的缓存。

一级缓存的实现说起来也简单,就是在查询的时候缓存,更新或者事务提交时清空缓存。

查询:

java 复制代码
// BaseExecutor.java
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    // 。。。省略大量代码
    // 检查一级缓存
    List<E> 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);
    }
    // ...省略大量代码
    return list;
}

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 {
        //调用子类必须实现的doQuery执行真正的查询。
        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;  
}

更新 & 提交

java 复制代码
@Override  
public int update(MappedStatement ms, Object parameter) throws SQLException {  
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());  
    if (closed) {  
        throw new ExecutorException("Executor was closed.");  
    }  
    //一旦更新,先清除缓存
    clearLocalCache();  
    //调用子类需要实现的doUpdate方法
    return doUpdate(ms, parameter);  
}

@Override  
public void commit(boolean required) throws SQLException {  
    if (closed) {  
        throw new ExecutorException("Cannot commit, transaction is already closed");  
    }
    // 提交前,清除缓存
    clearLocalCache(); 
    // 刷新statement,子类实现,BatchExecutor需要
    flushStatements();  
    if (required) {  
        transaction.commit();  
    }  
}

BaseExecutor是一个典型的模板模式,为子类提供了一级缓存的能力,子类只要继承它,并实现对应的方法,就可以实现缓存的能力。需要实现的方法有:

抽象方法 作用 子类实现差异
doQuery 执行查询操作(SELECT) 不同执行器创建和管理 Statement 的方式不同
doUpdate 执行更新操作(INSERT/UPDATE/DELETE) 批量执行器会攒批处理,而非立即执行
doFlushStatements 刷新statement(如批量操作的提交) BatchExecutor对更新批量提交;ReuseExecutor批量关闭statement;SimpleExecutor返回空列表
doQueryCursor 执行游标查询(返回 Cursor doQuery 类似,但返回游标用于流式处理

下面我们看看各个子类的功能以及如何实现的。

2. SimpleExecutor:简单执行器(默认实现)

SimpleExecutor是 MyBatis 的默认执行器,每次执行SQL都会创建新的Statement对象,执行完毕后立即关闭

doQuery为例,介绍SimpleExecutor的实现:

java 复制代码
@Override  
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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);  
        stmt = prepareStatement(handler, ms.getStatementLog());  
        return handler.<E>query(stmt, resultHandler);  
    } finally {
        // Statement对象使用后立即关闭
        closeStatement(stmt);  
    }  
}

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {  
    Statement stmt;  
    Connection connection = getConnection(statementLog);  
    stmt = handler.prepare(connection, transaction.getTimeout());  
    handler.parameterize(stmt);  
    return stmt;  
}

适用场景:大多数常规业务场景,尤其是SQL语句不重复、执行频率低的情况。由于无需维护Statement缓存,逻辑简单且稳定。

3. ReuseExecutor:可重用执行器

ReuseExecutor的核心是重用Statement对象 ,通过SQL语句作为key缓存Statement,避免重复创建。而Statement的关闭,则是在doFlushStatements时执行,也就是,在事务提交、回滚或者Executor关闭的时候,才对所有缓存的Statement进行关闭。

doQuery为例,介绍ReuseExecutor的实现:

java 复制代码
@Override  
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {  
    Configuration configuration = ms.getConfiguration();  
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);  
    // 注意这里使用完Statement后,并没有立即关闭
    Statement stmt = prepareStatement(handler, ms.getStatementLog());  
    return handler.<E>query(stmt, resultHandler);  
}

// Statement缓存,key是SQL,value是Statement
private final Map<String, Statement> statementMap = new HashMap<String, Statement>();

/**
 * 先尝试从statementMap获取Statement,没有才创建,并放到statementMap中
 */
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {  
    Statement stmt;  
    BoundSql boundSql = handler.getBoundSql();  
    String sql = boundSql.getSql();  
    if (hasStatementFor(sql)) {  
        stmt = getStatement(sql);  
        applyTransactionTimeout(stmt);  
    } else {  
        Connection connection = getConnection(statementLog);  
        stmt = handler.prepare(connection, transaction.getTimeout());  
        putStatement(sql, stmt);  
    }  
    handler.parameterize(stmt);  
    return stmt;  
}

/**
 * 批量关闭Statement,并清空缓存。
 */
@Override  
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {  
    for (Statement stmt : statementMap.values()) {  
        closeStatement(stmt);  
    }  
    statementMap.clear();  
    return Collections.emptyList();  
}

小结一下,与SimpleExecutor最大的不同是

  1. 缓存Statement,SQL完全相同,则直接复用,无需多次创建。
  2. 执行完成后没有立即关闭Statement,而是在事务提交、回滚或者Executor关闭时,统一关闭。

适用场景:需要频繁执行相同SQL的场景(如循环查询相同结构的语句),可减少Statement创建的性能开销。

4. BatchExecutor:批量执行器

BatchExecutor专为批量更新操作(INSERT/UPDATE/DELETE)设计,能将多个 SQL 攒批后统一提交,减少数据库交互次数。

BatchExecutor需要重点关注doUpdate方法。

java 复制代码
private final List<Statement> statementList = new ArrayList<>();
private final List<BatchResult> batchResults = new ArrayList<>();
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    final Configuration configuration = ms.getConfiguration();
    final StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    final BoundSql boundSql = handler.getBoundSql();
    final String sql = boundSql.getSql();
    final Statement stmt;
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
        // 若SQL相同且Statement未关闭,复用Statement添加批处理
        int last = statementList.size() - 1;
        stmt = statementList.get(last);
        applyTransactionTimeout(stmt);
        handler.parameterize(stmt); // 设置参数
        BatchResult batchResult = batchResults.get(last);
        batchResult.addParameterObject(parameter);
    } else {
        // 新SQL,创建新Statement
        Connection connection = getConnection(ms.getStatementLog());
        stmt = handler.prepare(connection, transaction.getTimeout());
        handler.parameterize(stmt); // 设置参数
        currentSql = sql;
        currentStatement = ms;
        statementList.add(stmt);
        batchResults.add(new BatchResult(ms.getId(), sql, parameter));
    }
    handler.batch(stmt); // 添加到批处理
    return BATCH_UPDATE_RETURN_VALUE;
}
// 提交批处理
@Override
public List <BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    try {
        List<BatchResult> results = new ArrayList<>();
        if (isRollback) {
            return Collections.emptyList();
        }
        // 执行所有批处理语句
        for (Statement stmt : statementList) {
            applyTransactionTimeout(stmt);
            BatchResult batchResult = batchResults.get(statementList.indexOf(stmt));
            try {
                //调用Statement的executeBatch实现批量
                batchResult.setUpdateCounts(stmt.executeBatch());
                // 。。。
            } catch (BatchUpdateException e) {
                // 处理异常
            }
            results.add(batchResult);
        }
        return results;
    } finally {
        // 清空缓存
        for (Statement stmt : statementList) {
            closeStatement(stmt);
        }
        currentSql = null;
        statementList.clear();
        batchResults.clear();
    }
}

另外,在doQuerydoQueryCursor方法中,也会先执行下flushStatements,以确保能查出最新的数据。

适用场景:批量导入、批量更新等场景,能显著减少数据库交互次数(如一次提交 1000 条插入语句)。

三、默认 Executor 与配置方式

1. 默认 Executor

MyBatis 的默认 Executor 是SimpleExecutor,这由Configuration类的默认配置指定:

ini 复制代码
// Configuration.java
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;

2. 更换默认 Executor

(1)全局配置(mybatis-config.xml)

通过settings标签配置默认执行器类型:

xml 复制代码
<configuration>
  <settings>
    <!-- 可选值:SIMPLE、REUSE、BATCH -->
    <setting name="defaultExecutorType" value="REUSE"/>
  </settings>
</configuration>
(2)创建 SqlSession 时指定

通过SqlSessionFactory.openSession(ExecutorType)动态指定:

java 复制代码
// 使用BATCH执行器
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);

3. BatchExecutor 使用示例

批量插入用户的完整示例:

java 复制代码
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
    UserMapper mapper = session.getMapper(UserMapper.class);
    
    // 批量添加1000条记录(仅入队,不立即执行)
    for (int i = 0; i < 1000; i++) {
        User user = new User();
        user.setName("User" + i);
        user.setAge(20 + i % 30);
        mapper.insert(user);
    }
    
    // 提交事务,执行批量插入
    session.commit();
} catch (Exception e) {
    // 异常回滚
    session.rollback();
}

注意:BatchExecutor仅对insert/update/delete有效,查询操作仍会立即执行。

四、小结

Executor是MyBatis SQL 执行的核心引擎,其设计体现了多种设计模式的灵活运用:

  • 模板方法模式:BaseExecutor定义执行流程,子类实现具体细节;
  • 策略模式:不同执行器对应不同 SQL 执行策略,按需选择。

另外,Executor还有一个实现类,CachingExecutor,采用装饰器模式,用于实现Mybatis的二级缓存,这个我们下期再讲。

相关推荐
我是哪吒16 小时前
分布式微服务系统架构第170集:Kafka消费者并发-多节点消费-可扩展性
后端·面试·github
纪元A梦16 小时前
贪心算法应用:数字孪生同步问题详解
java·算法·贪心算法
Micrle_00717 小时前
java分布式场景怎么实现一个高效的 读-写锁
java·分布式
海上生明月丿17 小时前
微服务01
java·spring boot·微服务
Badman17 小时前
分布式系统下的数据一致性-Redis分布式锁
redis·分布式·后端
Java水解17 小时前
盘点那些自带高级算法的SQL
后端
coooliang17 小时前
【鸿蒙 NEXT】V1迁移V2状态管理
java·前端·harmonyos
Luke Ewin17 小时前
FunASR的Java实现Paraformer实时语音识别 | 一款无需联网的本地实时字幕软件
java·人工智能·语音识别·asr·funasr·paraformer·sensevoice
叫我阿柒啊18 小时前
从Java全栈到前端框架的全面实战:一次真实面试的深度解析
java·spring boot·缓存·微服务·消息队列·vue3·rest api