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 { <<interface>> +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 { <<abstract>> -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<String, Statement> +prepareStatement(handler, statementLog) Statement } class BatchExecutor { -statementList: List<Statement> -batchResults: List<BatchResult> -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的二级缓存,这个我们下期再讲。

相关推荐
葫芦和十三2 小时前
图解 MongoDB 21|选举与 failover:Primary 是怎么选出来的
后端·mongodb·agent
GetcharZp2 小时前
26k Star 开源内网穿透神器 NetBird,一分钟实现全球设备互联!
后端
考虑考虑3 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯3 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式
lizhongxuan6 小时前
多Agent之间的区别
后端
青石路7 小时前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
杨充8 小时前
1.面向对象设计思想
后端
IT_陈寒8 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
systemPro9 小时前
2.6亿条设备数据,历史查询从超时到50ms,我做了什么
后端
要阿尔卑斯吗9 小时前
提示词优化启示:为什么“按顺序输出“比“关键度评分“更有效
后端