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)一级缓存清空时机
一级缓存会在以下场景自动清空,避免脏读:
- 执行
update/insert/delete方法时(BaseExecutor.update()先调用clearLocalCache()); - 调用
SqlSession.commit()/rollback()时; - 调用
SqlSession.clearCache()时; - 本地缓存作用域为
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 {
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<>();
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
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
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
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(); // 二级缓存管理器
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 个条件才生效:
全局开关:
mybatis-config.xml中cacheEnabled=true(默认 true):<settings> <setting name="cacheEnabled" value="true"/> </settings>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>语句开关:
<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 一次),避免数据库连接超时。