mybatis之执行器Executor

MyBatis 执行器(Executor)深度解析:从架构到实战

Executor 是 MyBatis 的核心执行引擎,负责数据库操作的调度、缓存管理与事务控制,是 SqlSession 方法的实际执行者。MyBatis 通过 ExecutorType 提供三种基础执行器(SimpleExecutor/ReuseExecutor/BatchExecutor),并通过 CachingExecutor 实现二级缓存,形成 “基础执行 + 缓存增强” 的装饰器架构。从 “架构设计→核心实现→缓存机制→实战选型” 四个维度,彻底拆解 Executor 的工作原理。

Executor 核心定位与架构

核心职责

Executor 是 MyBatis 与数据库交互的 “总指挥”,核心职责包括:

  • 调度 StatementHandler/ParameterHandler/ResultSetHandler 完成 SQL 执行;
  • 管理一级缓存(会话级)和二级缓存(应用级);
  • 控制事务提交 / 回滚,处理批量 SQL 执行;
  • 生成缓存键(CacheKey),实现缓存命中逻辑。

架构设计:接口 + 抽象基类 + 具体实现

MyBatis 采用 “接口定义规范 + 抽象类封装通用逻辑 + 具体类实现差异化功能” 的设计,同时通过装饰器模式CachingExecutor)增强缓存能力:

graph TD
    A[Executor 接口] --> B[BaseExecutor 抽象类]
    B --> C[SimpleExecutor 简易执行器]
    B --> D[ReuseExecutor 复用执行器]
    B --> E[BatchExecutor 批量执行器]
    A --> F[CachingExecutor 缓存执行器 装饰器]
    F --> B[包装 BaseExecutor 子类]
  • 接口(Executor):定义数据库操作的核心方法(update/query/commit/rollback 等);
  • 抽象基类(BaseExecutor):实现通用逻辑(一级缓存、事务管理、延迟加载),暴露 doUpdate/doQuery 等抽象方法由子类实现;
  • 具体执行器:实现差异化 SQL 执行逻辑(如 Statement 复用、批量处理);
  • 装饰器(CachingExecutor):包装基础执行器,添加二级缓存功能,不改变原有执行逻辑。

ExecutorType 枚举:执行器类型

MyBatis 通过 ExecutorType 枚举指定执行器类型,决定 SQL 执行策略:

ExecutorType 对应执行器 核心特点 适用场景
SIMPLE SimpleExecutor 每次执行 SQL 新建 Statement,执行后关闭 大多数单条 SQL 场景(默认类型)
REUSE ReuseExecutor 按 SQL 缓存 Statement,复用避免重建 重复执行相同 SQL(如循环查询)
BATCH BatchExecutor 缓存多条 SQL,批量提交 批量插入 / 更新 / 删除(减少网络开销)

Executor 的创建流程

Executor 通过 Configuration.newExecutor() 方法创建,核心逻辑是 “根据 ExecutorType 生成基础执行器,若开启二级缓存则用 CachingExecutor 包装”:

1. 核心源码:Configuration.newExecutor ()

// org.apache.ibatis.session.Configuration
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // 1. 若未指定 ExecutorType,使用默认值 SIMPLE
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;

    Executor executor;
    // 2. 根据 ExecutorType 生成基础执行器
    switch (executorType) {
        case BATCH:
            executor = new BatchExecutor(this, transaction);
            break;
        case REUSE:
            executor = new ReuseExecutor(this, transaction);
            break;
        case SIMPLE:
        default:
            executor = new SimpleExecutor(this, transaction);
            break;
    }

    // 3. 若开启二级缓存(cacheEnabled=true),用 CachingExecutor 包装基础执行器
    if (cacheEnabled) {
        executor = new CachingExecutor(executor);
    }

    // 4. 应用插件(拦截器),生成代理执行器
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

关键细节:

  • 装饰器模式CachingExecutor 不直接执行 SQL,而是委托给包装的基础执行器(如 SimpleExecutor),仅在执行前后添加缓存逻辑;
  • 插件支持:通过 interceptorChain.pluginAll() 生成代理对象,支持拦截 Executor 方法(如 PageHelper 拦截 query 方法实现分页)。

BaseExecutor:抽象基类的通用逻辑

BaseExecutor 是所有基础执行器的父类,封装了一级缓存、事务管理、延迟加载等通用逻辑,暴露 4 个抽象方法(doUpdate/doQuery/doQueryCursor/doFlushStatements)由子类实现。

1. 核心属性与一级缓存

public abstract class BaseExecutor implements Executor {
    protected Transaction transaction; // 事务对象
    protected Executor wrapper;         // 包装的执行器(用于装饰器模式)
    protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads; // 延迟加载队列
    protected PerpetualCache localCache; // 一级缓存(会话级,默认内存实现)
    protected PerpetualCache localOutputParameterCache; // 存储过程输出参数缓存
    protected Configuration configuration;
    protected int queryStack; // 嵌套查询层数(防止递归查询缓存问题)
    private boolean closed;
}
一级缓存(LocalCache)核心机制:

一级缓存是会话级缓存,生命周期与 SqlSession 一致(即与 Executor 生命周期一致),默认开启,无需额外配置。

(1)缓存命中逻辑(query 方法)
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);
}

public <E> List<E> query(...) throws SQLException {
    if (closed) throw new ExecutorException("Executor was closed.");
    // 非嵌套查询且需清空缓存(如 <select flushCache="true">),则清空一级缓存
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
        clearLocalCache();
    }

    List<E> list;
    try {
        queryStack++;
        // 1. 先查一级缓存
        list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
        if (list != null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); // 处理存储过程输出参数
        } else {
            // 2. 缓存未命中,查数据库
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
    } finally {
        queryStack--;
    }

    // 3. 嵌套查询结束后,处理延迟加载队列
    if (queryStack == 0) {
        for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        deferredLoads.clear();
        // 若本地缓存作用域为 STATEMENT(语句级),则清空缓存
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            clearLocalCache();
        }
    }
    return list;
}
(2)缓存键(CacheKey)的组成

缓存命中的关键是 CacheKey,由 6 个维度组成,确保 “相同查询” 的唯一性:

public CacheKey createCacheKey(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) {
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());          // 1. Mapper 方法唯一标识(namespace + methodId)
    cacheKey.update(rowBounds.getOffset());// 2. 分页偏移量
    cacheKey.update(rowBounds.getLimit()); // 3. 分页条数
    cacheKey.update(boundSql.getSql());   // 4. 最终执行的 SQL 语句(动态 SQL 解析后)
    // 5. SQL 参数值(过滤输出参数)
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    for (ParameterMapping pm : parameterMappings) {
        if (pm.getMode() != ParameterMode.OUT) {
            Object value = boundSql.hasAdditionalParameter(pm.getProperty()) 
                ? boundSql.getAdditionalParameter(pm.getProperty()) 
                : (parameter == null ? null : typeHandlerRegistry.hasTypeHandler(parameter.getClass()) 
                    ? parameter : configuration.newMetaObject(parameter).getValue(pm.getProperty()));
            cacheKey.update(value);
        }
    }
    // 6. 数据库环境 ID(多环境部署时区分)
    if (configuration.getEnvironment() != null) {
        cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
}
(3)一级缓存清空时机

一级缓存会在以下场景自动清空,避免脏读:

  1. 执行 update/insert/delete 方法时(BaseExecutor.update() 先调用 clearLocalCache());
  2. 调用 SqlSession.commit()/rollback() 时;
  3. 调用 SqlSession.clearCache() 时;
  4. 本地缓存作用域为 STATEMENT(语句级)时,执行完 SQL 立即清空。

2. 延迟加载(DeferredLoad)

MyBatis 支持嵌套查询的延迟加载(如一对一 / 一对多关联查询),deferredLoads 队列用于暂存未加载的关联属性,待外层查询结束后统一加载:

public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
    DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
    if (deferredLoad.canLoad()) { // 若缓存已存在关联数据,立即加载
        deferredLoad.load();
    } else {
        deferredLoads.add(deferredLoad); // 否则加入延迟队列
    }
}
  • 触发时机:外层查询结束(queryStack == 0)时,遍历 deferredLoads 执行 load(),从一级缓存中获取关联数据并设置到外层对象。

3. 抽象方法:子类实现差异化逻辑

BaseExecutor 暴露 4 个抽象方法,由子类实现具体的 SQL 执行逻辑:

// 执行 insert/update/delete
protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
// 执行批量 SQL 刷新
protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;
// 执行查询(返回 List)
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
// 执行查询(返回 Cursor,流式读取)
protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException;

三大基础执行器:差异化实现

1. SimpleExecutor:默认简易执行器

SimpleExecutor 是 MyBatis 的默认执行器,每次执行 SQL 都创建新的 Statement,执行后立即关闭,逻辑简单但无复用优化。

核心源码:doQuery 方法
public class SimpleExecutor extends BaseExecutor {
    @Override
    public <E> List<E> doQuery(...) throws SQLException {
        Statement stmt = null;
        try {
            Configuration config = ms.getConfiguration();
            // 1. 创建 StatementHandler(路由到具体实现,如 PreparedStatementHandler)
            StatementHandler handler = config.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            // 2. 预编译 SQL + 设置参数(委托给 StatementHandler)
            stmt = prepareStatement(handler, ms.getStatementLog());
            // 3. 执行查询并解析结果(委托给 StatementHandler)
            return handler.query(stmt, resultHandler);
        } finally {
            // 4. 执行后关闭 Statement(关键:无复用)
            closeStatement(stmt);
        }
    }

    // 预编译 SQL + 设置参数
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Connection conn = getConnection(statementLog);
        Statement stmt = handler.prepare(conn, transaction.getTimeout()); // 预编译 SQL
        handler.parameterize(stmt); // 设置参数(委托给 ParameterHandler)
        return stmt;
    }
}
特点与适用场景
优点 缺点 适用场景
逻辑简单,无额外缓存开销 频繁创建 / 关闭 Statement,性能损耗较大 单条 SQL 执行、短期查询(默认场景)
避免 Statement 复用导致的线程安全问题 不适合循环执行相同 SQL(如批量查询) 开发环境、小流量场景

2. ReuseExecutor:复用 Statement 执行器

ReuseExecutor 通过 Map 缓存 Statement(key=SQL 语句,value=Statement),相同 SQL 可复用 Statement,减少创建 / 关闭开销。

核心源码:缓存与复用逻辑
public class ReuseExecutor extends BaseExecutor {
    // 缓存 Statement:key=SQL 语句,value=Statement 对象
    private final Map<String, Statement> statementMap = new HashMap<>();

    @Override
    public <E> List<E> doQuery(...) throws SQLException {
        Statement stmt = null;
        try {
            Configuration config = ms.getConfiguration();
            StatementHandler handler = config.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            stmt = prepareStatement(handler, ms.getStatementLog()); // 复用 Statement 的核心
            return handler.query(stmt, resultHandler);
        } finally {
            // 关键:不关闭 Statement,仅在 flush 或关闭时清理
            // closeStatement(stmt); 
        }
    }

    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        BoundSql boundSql = handler.getBoundSql();
        String sql = boundSql.getSql();
        Statement stmt;

        // 1. 检查缓存中是否有该 SQL 对应的 Statement(且连接未关闭)
        if (hasStatementFor(sql)) {
            stmt = getStatement(sql);
            applyTransactionTimeout(stmt); // 更新超时时间
        } else {
            // 2. 缓存未命中,创建新 Statement 并缓存
            Connection conn = getConnection(statementLog);
            stmt = handler.prepare(conn, transaction.getTimeout());
            statementMap.put(sql, stmt); // 存入缓存
        }

        // 3. 设置参数(无论是否复用,都需重新设置参数)
        handler.parameterize(stmt);
        return stmt;
    }

    // 检查 Statement 是否可用(非空且连接未关闭)
    private boolean hasStatementFor(String sql) {
        try {
            Statement stmt = statementMap.get(sql);
            return stmt != null && !stmt.getConnection().isClosed();
        } catch (SQLException e) {
            return false;
        }
    }

    // 刷新时关闭所有缓存的 Statement 并清空 Map
    @Override
    public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
        for (Statement stmt : statementMap.values()) {
            closeStatement(stmt);
        }
        statementMap.clear();
        return Collections.emptyList();
    }
}
特点与适用场景
优点 缺点 适用场景
复用 Statement,减少创建 / 关闭开销 缓存需占用内存,多线程下需注意连接关闭问题 循环执行相同 SQL(如批量查询)
提升重复 SQL 的执行效率 不支持批量更新(需用 BatchExecutor) 高频重复查询场景(如商品列表查询)

3. BatchExecutor:批量执行器

BatchExecutor 专为批量 insert/update/delete 设计,通过缓存多条 SQL 并一次性提交,减少网络通信开销(JDBC 批量仅支持 DML,不支持 SELECT)。

核心源码:批量缓存与提交逻辑
public class BatchExecutor extends BaseExecutor {
    // 缓存批量 Statement(每个 Statement 存储多条 SQL)
    private final List<Statement> statementList = new ArrayList<>();
    // 批量结果记录(每个 BatchResult 对应一个 Statement 的执行结果)
    private final List<BatchResult> batchResultList = new ArrayList<>();
    private String currentSql; // 当前执行的 SQL(用于判断是否复用 Statement)
    private MappedStatement currentStatement; // 当前 MappedStatement

    @Override
    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Configuration config = ms.getConfiguration();
        StatementHandler handler = config.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
        BoundSql boundSql = handler.getBoundSql();
        String sql = boundSql.getSql();
        Statement stmt;

        // 1. 若当前 SQL 和 MappedStatement 与上次相同,复用 Statement
        if (sql.equals(currentSql) && ms.equals(currentStatement)) {
            int lastIdx = statementList.size() - 1;
            stmt = statementList.get(lastIdx);
            applyTransactionTimeout(stmt);
            handler.parameterize(stmt); // 设置参数
            BatchResult batchResult = batchResultList.get(lastIdx);
            batchResult.addParameterObject(parameter); // 记录参数
        } else {
            // 2. 新建 Statement 并加入缓存
            Connection conn = getConnection(ms.getStatementLog());
            stmt = handler.prepare(conn, transaction.getTimeout());
            handler.parameterize(stmt); // 设置参数
            currentSql = sql;
            currentStatement = ms;
            statementList.add(stmt);
            batchResultList.add(new BatchResult(ms, sql, parameter));
        }

        // 3. 加入批量 SQL 队列(不立即执行)
        handler.batch(stmt);
        return BATCH_UPDATE_RETURN_VALUE; // 批量执行无实时返回值,返回固定标识
    }

    // 刷新批量 SQL:执行所有缓存的 Statement
    @Override
    public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
        try {
            List<BatchResult> results = new ArrayList<>();
            if (!isRollback) {
                for (int i = 0; i < statementList.size(); i++) {
                    Statement stmt = statementList.get(i);
                    applyTransactionTimeout(stmt);
                    BatchResult batchResult = batchResultList.get(i);
                    try {
                        // 1. 批量执行 SQL(关键:一次性提交)
                        batchResult.setUpdateCounts(stmt.executeBatch());
                        MappedStatement ms = batchResult.getMappedStatement();
                        KeyGenerator keyGenerator = ms.getKeyGenerator();
                        // 2. 处理自增主键(如 JDBC3KeyGenerator 批量获取主键)
                        if (keyGenerator instanceof Jdbc3KeyGenerator) {
                            ((Jdbc3KeyGenerator) keyGenerator).processBatch(ms, stmt, batchResult.getParameterObjects());
                        }
                        results.add(batchResult);
                    } catch (BatchUpdateException e) {
                        // 批量执行异常:抛出并携带已成功的结果
                        throw new BatchExecutorException(
                            "Batch execution failed for " + ms.getId(), e, results, batchResult);
                    } finally {
                        closeStatement(stmt);
                    }
                }
            }
            return results;
        } finally {
            // 清空缓存
            for (Statement stmt : statementList) {
                closeStatement(stmt);
            }
            currentSql = null;
            currentStatement = null;
            statementList.clear();
            batchResultList.clear();
        }
    }
}
关键细节:
  • 返回值doUpdate 返回 BATCH_UPDATE_RETURN_VALUE(-2147482646),表示批量执行无实时行数,需通过 flushStatements() 获取结果;
  • 提交时机:批量 SQL 仅在 flushStatements()(手动调用或事务提交时)执行,减少网络请求;
  • 主键处理:支持批量获取自增主键(如 MySQL 的 LAST_INSERT_ID()),需配置 useGeneratedKeys=true
特点与适用场景
优点 缺点 适用场景
批量提交 SQL,减少网络开销 仅支持 DML(insert/update/delete),不支持 SELECT 批量数据导入(如 Excel 导入)
支持批量获取自增主键 需手动调用 flush 或提交事务才执行 高频批量更新(如订单状态同步)

CachingExecutor:二级缓存装饰器

CachingExecutor 是 Executor 的装饰器实现,不改变基础执行器的 SQL 执行逻辑,仅在执行前后添加二级缓存(应用级缓存)的查询与更新。

1. 二级缓存核心架构

二级缓存通过 “CachingExecutor + TransactionalCacheManager + TransactionalCache” 实现,核心是 “事务提交时才写入缓存,回滚时丢弃”,确保数据一致性:

graph TD
    A[CachingExecutor] --> B[TransactionalCacheManager 缓存管理器]
    B --> C[Map<Cache, TransactionalCache> 缓存映射]
    C --> D[TransactionalCache 事务缓存]
    D --> E[Delegate Cache 实际缓存 如 PerpetualCache]
    D --> F[entriesToAddOnCommit 待提交缓存项]
  • TransactionalCacheManager:管理多个二级缓存(按 Mapper 命名空间区分),每个缓存对应一个 TransactionalCache
  • TransactionalCache:事务级缓存代理,暂存待提交的缓存项(entriesToAddOnCommit),事务提交时写入实际缓存,回滚时丢弃;
  • Delegate Cache:实际缓存实现(默认 PerpetualCache,可通过 <cache type="xxx"> 自定义,如 Redis 缓存)。

2. 核心源码:二级缓存查询与提交

(1)查询逻辑(先查二级缓存,再查一级缓存 / 数据库)
public class CachingExecutor implements Executor {
    private final Executor delegate; // 包装的基础执行器(如 SimpleExecutor)
    private final TransactionalCacheManager tcm = new TransactionalCacheManager(); // 二级缓存管理器

    @Override
    public <E> List<E> query(...) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

    public <E> List<E> query(...) throws SQLException {
        // 1. 获取当前 Mapper 命名空间的二级缓存(由 <cache> 标签配置)
        Cache cache = ms.getCache();
        if (cache != null) {
            // 2. 若需清空缓存(如 <select flushCache="true">),则清空二级缓存
            flushCacheIfRequired(ms);
            // 3. 检查是否启用二级缓存(useCache=true 且无 ResultHandler)
            if (ms.isUseCache() && resultHandler == null) {
                ensureNoOutParams(ms, boundSql); // 二级缓存不支持存储过程输出参数
                // 4. 查二级缓存(委托给 TransactionalCache)
                List<E> list = (List<E>) tcm.getObject(cache, key);
                if (list == null) {
                    // 5. 二级缓存未命中,查基础执行器(一级缓存/数据库)
                    list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    // 6. 暂存缓存项(事务提交时才写入二级缓存)
                    tcm.putObject(cache, key, list);
                }
                return list;
            }
        }
        // 7. 未启用二级缓存,直接查基础执行器
        return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
}
(2)事务提交:写入二级缓存
// TransactionalCache.commit():事务提交时将暂存项写入实际缓存
public void commit() {
    if (clearOnCommit) {
        delegate.clear(); // 若标记清空,则先清空实际缓存
    }
    flushPendingEntries(); // 将 entriesToAddOnCommit 写入实际缓存
    reset();
}

private void flushPendingEntries() {
    // 1. 写入待提交的缓存项
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
        delegate.putObject(entry.getKey(), entry.getValue());
    }
    // 2. 对缓存未命中的键,写入 null(避免重复查询数据库)
    for (Object key : entriesMissedInCache) {
        if (!entriesToAddOnCommit.containsKey(key)) {
            delegate.putObject(key, null);
        }
    }
}
(3)事务回滚:丢弃缓存项
public void rollback() {
    unlockMissedEntries(); // 移除缓存未命中的键
    reset();
}

private void unlockMissedEntries() {
    for (Object key : entriesMissedInCache) {
        try {
            delegate.removeObject(key);
        } catch (Exception e) {
            log.warn("Unexpected exception while notifying a rollback to the cache adapter.", e);
        }
    }
}

3. 二级缓存配置开关

二级缓存需满足以下 3 个条件才生效:

  1. 全局开关:mybatis-config.xmlcacheEnabled=true(默认 true):

    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>
  2. Mapper 开关:在 Mapper XML 中配置<cache><cache-ref>

    • <cache>:为当前命名空间创建独立二级缓存(默认 PerpetualCache);
    • <cache-ref namespace="com.xxx.OtherMapper">:共享其他命名空间的缓存。
    <!-- 为 UserMapper 配置二级缓存 -->
    <mapper namespace="com.example.mybatis.mapper.UserMapper">
        <cache eviction="LRU" flushInterval="60000" size="1024" readOnly="true"/>
        <!-- eviction:缓存回收策略(LRU 最近最少使用);flushInterval:自动刷新间隔(ms);size:缓存最大条目;readOnly:是否只读 -->
    </mapper>
  3. 语句开关:<select>标签的useCache=true(默认 true,可单独关闭):

    <!-- 该查询不写入二级缓存 -->
    <select id="selectUser" resultType="user" useCache="false">
        SELECT * FROM user WHERE id = #{id}
    </select>

一级缓存 vs 二级缓存:核心差异

对比维度 一级缓存(LocalCache) 二级缓存(CachingExecutor)
作用范围 会话级(SqlSession/Executor 生命周期) 应用级(MyBatis 启动到关闭)
存储位置 内存(PerpetualCache),不可自定义 可自定义(内存 / Redis/EHCache)
共享性 仅当前 SqlSession 可见,不共享 所有 SqlSession 共享(按命名空间隔离)
生效条件 默认开启,无需额外配置 需开启 cacheEnabled + 配置 <cache>
清空时机 update/commit/rollback/clearCache 事务提交时写入,回滚时丢弃;<cache flushInterval> 自动刷新
适用场景 单次会话内重复查询 多会话共享的高频查询(如商品分类列表)

Executor 实战选型与最佳实践

1. 执行器选型建议

业务场景 推荐执行器 理由
单条 SQL 执行(如详情查询) SimpleExecutor 逻辑简单,无额外开销
循环执行相同 SQL(如批量查询) ReuseExecutor 复用 Statement,减少创建 / 关闭开销
批量 insert/update/delete(如导入) BatchExecutor 批量提交,减少网络请求
多会话共享高频查询(如首页数据) CachingExecutor + 二级缓存 应用级缓存,减少数据库访问

2. 缓存使用注意事项

  • 一级缓存避免脏读:若同一会话内有更新操作,需注意一级缓存未清空导致的脏读(可通过 flushCache="true" 强制清空);
  • 二级缓存谨慎共享:通过 cache-ref 共享缓存时,需确保关联命名空间的更新操作会同步清空缓存(避免跨命名空间脏读);
  • 自定义二级缓存:生产环境建议用 Redis/EHCache 替代默认内存缓存(避免应用重启缓存丢失),需实现 Cache 接口;
  • 禁用不必要的缓存:对实时性要求高的数据(如订单状态),禁用二级缓存(useCache="false")。

3. 批量执行最佳实践

  • BatchExecutor 配合 SqlSession 批量提交:

    try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
        UserMapper mapper = session.getMapper(UserMapper.class);
        for (User user : userList) {
            mapper.insertUser(user); // 加入批量队列,不立即执行
        }
        session.flushStatements(); // 手动触发批量执行(可选,commit 时会自动触发)
        session.commit(); // 提交事务,自动执行 flushStatements()
    }
  • 控制批量大小:若批量数据量过大(如 10 万条),建议分批次提交(如每 1000 条 flush 一次),避免数据库连接超时。