0%

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)增强缓存能力:

Executor 接口
包装 BaseExecutor 子类
SimpleExecutor 简易执行器
ReuseExecutor 复用执行器
BatchExecutor 批量执行器
CachingExecutor 缓存执行器 装饰器
  • 接口(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 ()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 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. 核心属性与一级缓存

1
2
3
4
5
6
7
8
9
10
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 方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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 个维度组成,确保 “相同查询” 的唯一性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 队列用于暂存未加载的关联属性,待外层查询结束后统一加载:

1
2
3
4
5
6
7
8
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 执行逻辑:

1
2
3
4
5
6
7
8
// 执行 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 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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,减少创建 / 关闭开销。

核心源码:缓存与复用逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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)。

核心源码:批量缓存与提交逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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” 实现,核心是 “事务提交时才写入缓存,回滚时丢弃”,确保数据一致性:

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

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

(1)查询逻辑(先查二级缓存,再查一级缓存 / 数据库)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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)事务提交:写入二级缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 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)事务回滚:丢弃缓存项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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):

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

    • <cache>:为当前命名空间创建独立二级缓存(默认 PerpetualCache);
    • <cache-ref namespace="com.xxx.OtherMapper">:共享其他命名空间的缓存。
    1
    2
    3
    4
    5
    <!-- 为 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,可单独关闭):

    1
    2
    3
    4
    <!-- 该查询不写入二级缓存 -->
    <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 批量提交:

    1
    2
    3
    4
    5
    6
    7
    8
    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 一次),避免数据库连接超时。

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10