本文深入剖析mybatis的sql执行模块,带你全面理解executor执行器体系、缓存机制、事务管理和批处理原理。
一、mybatis整体架构与sql执行模块
在深入sql执行模块之前,我们先了解mybatis的整体架构,以及sql执行模块在其中的核心地位。

1.1 sql执行模块的核心职责
sql执行模块主要承担以下核心职责:
1、sql执行:根据mappedstatement执行sql语句,并返回结果 2、缓存管理:管理一级缓存和二级缓存,提高查询性能 3、事务管理:控制数据库事务的提交、回滚和关闭 4、批处理支持:支持批量操作,提升数据修改效率 5、statement管理:管理jdbc statement对象的生命周期 6、插件拦截:提供拦截点,支持插件扩展
1.2 executor接口体系
executor是sql执行模块的顶层接口,定义了sql执行的基本方法:
public interface executor {
// 执行查询(带缓存key)
<e> list<e> query(mappedstatement ms, object parameter, rowbounds rowbounds, cachekey cachekey, boundsql boundsql);
// 执行查询
<e> list<e> query(mappedstatement ms, object parameter, rowbounds rowbounds, resulthandler resulthandler);
// 执行更新(插入、更新、删除)
int update(mappedstatement ms, object parameter);
// 刷新批量操作
list<batchresult> flushstatements();
// 提交事务
void commit(boolean required);
// 回滚事务
void rollback(boolean required);
// 创建cachekey
cachekey createcachekey(mappedstatement ms, object parameter, rowbounds rowbounds, boundsql boundsql);
// 判断是否缓存
boolean iscached(mappedstatement ms, cachekey cachekey);
// 清空本地缓存
void clearlocalcache();
// 获取事务
transaction gettransaction();
// 关闭执行器
void close(boolean forcerollback);
}
二、executor执行器架构
mybatis提供了多种executor实现,以适应不同的使用场景。

2.1 executor继承体系
executor采用了装饰器模式,提供了灵活的功能扩展:
executor (接口) ├── baseexecutor (抽象基类) │ ├── simpleexecutor (简单执行器) │ ├── reuseexecutor (可重用执行器) │ └── batchexecutor (批处理执行器) └── cachingexecutor (缓存执行器)
2.2 baseexecutor抽象基类
baseexecutor实现了executor接口的大部分功能,定义了sql执行的基本流程:
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;
@override
public <e> list<e> query(mappedstatement ms, object parameter, rowbounds rowbounds, resulthandler resulthandler) {
// 1. 创建boundsql
boundsql boundsql = ms.getboundsql(parameter);
// 2. 创建cachekey
cachekey key = createcachekey(ms, parameter, rowbounds, boundsql);
// 3. 执行查询
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) {
// 检查本地缓存
list<e> list = resulthandler == null ? (list<e>) localcache.getobject(key) : null;
if (list != null) {
return list;
}
// 执行数据库查询
list = queryfromdatabase(ms, parameter, rowbounds, resulthandler, key, boundsql);
return list;
}
}
2.3 simpleexecutor简单执行器
simpleexecutor是最基础的执行器实现,每次执行sql都会创建新的statement对象:
public class simpleexecutor extends baseexecutor {
@override
public <e> list<e> doquery(mappedstatement ms, object parameter, rowbounds rowbounds,
resulthandler resulthandler, boundsql boundsql) throws sqlexception {
statement stmt = null;
try {
// 1. 创建configuration对象
configuration configuration = ms.getconfiguration();
// 2. 创建statementhandler
statementhandler handler = configuration.newstatementhandler(wrapper, ms, parameter,
rowbounds, resulthandler, boundsql);
// 3. 创建statement
stmt = preparestatement(handler, ms.getstatementlog());
// 4. 执行查询
return handler.<e>query(stmt, resulthandler);
} finally {
// 5. 关闭statement
closestatement(stmt);
}
}
@override
public int doupdate(mappedstatement ms, object parameter) throws sqlexception {
statement stmt = null;
try {
configuration configuration = ms.getconfiguration();
statementhandler handler = configuration.newstatementhandler(this, ms, parameter,
rowbounds.default, null, null);
stmt = preparestatement(handler, ms.getstatementlog());
return handler.update(stmt);
} finally {
closestatement(stmt);
}
}
}
2.4 reuseexecutor可重用执行器
reuseexecutor会缓存statement对象,相同sql可以重用statement,减少statement创建开销:
public class reuseexecutor extends baseexecutor {
private final map<string, statement> statementmap = new hashmap<>();
@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 stmt = preparestatement(handler, ms.getstatementlog(), boundsql.getsql());
return handler.<e>query(stmt, resulthandler);
}
private statement preparestatement(statementhandler handler, log statementlog, string sql) throws sqlexception {
statement stmt;
// 尝试从缓存中获取statement
stmt = statementmap.get(sql);
if (stmt == null) {
// 缓存未命中,创建新的statement
stmt = preparestatement(handler, statementlog);
statementmap.put(sql, stmt);
}
return stmt;
}
}
2.5 batchexecutor批处理执行器
batchexecutor专门用于批量操作,会将多个sql语句批量执行:
public class batchexecutor extends baseexecutor {
private final list<statement> statementlist = new arraylist<>();
private final list<batchresult> batchresultlist = new arraylist<>();
private string currentsql;
private mappedstatement currentstatement;
@override
public int doupdate(mappedstatement ms, object parameterobject) throws sqlexception {
configuration configuration = ms.getconfiguration();
statementhandler handler = configuration.newstatementhandler(this, ms, parameterobject,
rowbounds.default, null, null);
boundsql boundsql = ms.getboundsql(parameterobject);
string sql = boundsql.getsql();
statement stmt;
// 检查是否需要切换sql
if (sql.equals(currentsql) && ms.equals(currentstatement)) {
// 相同sql,复用statement
int last = statementlist.size() - 1;
stmt = statementlist.get(last);
} else {
// 不同sql,创建新statement
currentsql = sql;
currentstatement = ms;
stmt = preparestatement(handler);
statementlist.add(stmt);
batchresultlist.add(new batchresult(ms, sql, parameterobject));
}
// 添加批处理
handler.parameterize(stmt);
handler.batch(stmt);
return batch_update_return_value;
}
@override
public list<batchresult> doflushstatements(boolean isrollback) throws sqlexception {
list<batchresult> results = new arraylist<>();
try {
for (int i = 0, n = statementlist.size(); i < n; i++) {
statement stmt = statementlist.get(i);
batchresult batchresult = batchresultlist.get(i);
try {
if (!isrollback) {
// 执行批处理
int[] updatecounts = stmt.executebatch();
batchresult.setupdatecounts(updatecounts);
}
results.add(batchresult);
} catch (sqlexception e) {
throw new batchexecutorexception("error updating database. cause: " + e, e, batchresult);
}
}
return results;
} finally {
// 清空缓存
statementlist.clear();
batchresultlist.clear();
currentsql = null;
currentstatement = null;
}
}
}
2.6 cachingexecutor缓存执行器
cachingexecutor是executor的装饰器,在底层executor之上增加了二级缓存功能:
public class cachingexecutor implements executor {
private final executor delegate;
private final transactionalcachemanager tcm = new transactionalcachemanager();
@override
public <e> list<e> query(mappedstatement ms, object parameter, rowbounds rowbounds,
resulthandler resulthandler) throws sqlexception {
// 1. 获取boundsql
boundsql boundsql = ms.getboundsql(parameter);
// 2. 创建cachekey
cachekey key = createcachekey(ms, parameter, rowbounds, boundsql);
// 3. 查询缓存
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 {
// 1. 检查二级缓存
cache cache = ms.getcache();
if (cache != null) {
// 刷新缓存(如果需要)
flushcacheifrequired(ms);
// 检查缓存是否命中
if (ms.isusecache() && resulthandler == null) {
list<e> list = (list<e>) tcm.getobject(cache, key);
if (list != null) {
return list;
}
}
}
// 2. 缓存未命中,委托给底层executor执行
list<e> list = delegate.<e>query(ms, parameter, rowbounds, resulthandler, key, boundsql);
// 3. 将结果放入二级缓存
if (cache != null) {
tcm.putobject(cache, key, list);
}
return list;
}
}
三、sql执行流程
sql的执行流程是executor的核心工作流程。

3.1 完整执行流程
以查询操作为例,完整的sql执行流程如下:
// 1. sqlsession调用executor
public <e> list<e> selectlist(string statement, object parameter, rowbounds rowbounds) {
try {
// 1.1 获取mappedstatement
mappedstatement ms = configuration.getmappedstatement(statement);
// 1.2 调用executor执行查询
return executor.query(ms, wrapcollection(parameter), rowbounds, executor.no_result_handler);
} catch (exception e) {
throw exceptionfactory.wrapexception("error querying database. cause: " + e, e);
}
}
// 2. executor执行查询
@override
public <e> list<e> query(mappedstatement ms, object parameter, rowbounds rowbounds, resulthandler resulthandler) {
// 2.1 获取boundsql
boundsql boundsql = ms.getboundsql(parameter);
// 2.2 创建cachekey
cachekey key = createcachekey(ms, parameter, rowbounds, boundsql);
// 2.3 执行查询
return query(ms, parameter, rowbounds, resulthandler, key, boundsql);
}
// 3. 检查一级缓存
@override
public <e> list<e> query(mappedstatement ms, object parameter, rowbounds rowbounds,
resulthandler resulthandler, cachekey key, boundsql boundsql) {
list<e> list;
// 3.1 检查一级缓存
if (resulthandler == null) {
list = (list<e>) localcache.getobject(key);
}
if (list != null) {
return list;
}
// 3.2 缓存未命中,查询数据库
list = queryfromdatabase(ms, parameter, rowbounds, resulthandler, key, boundsql);
return list;
}
// 4. 查询数据库
private <e> list<e> queryfromdatabase(mappedstatement ms, object parameter, rowbounds rowbounds,
resulthandler resulthandler, cachekey key, boundsql boundsql) {
list<e> list;
// 4.1 占位缓存,处理循环依赖
localcache.putobject(key, execution_placeholder);
try {
// 4.2 执行查询
list = doquery(ms, parameter, rowbounds, resulthandler, boundsql);
} finally {
// 4.3 移除占位符
localcache.removeobject(key);
}
// 4.4 将结果放入一级缓存
localcache.putobject(key, list);
// 4.5 处理延迟加载
if (ms.getconfiguration().islazyloadingenabled()) {
if (deferredloads != null && !deferredloads.isempty()) {
deferredloads.clear();
}
}
return list;
}
// 5. 执行实际查询
protected abstract <e> list<e> doquery(mappedstatement ms, object parameter, rowbounds rowbounds,
resulthandler resulthandler, boundsql boundsql) throws sqlexception;
3.2 statementhandler的作用
statementhandler负责statement的创建、参数设置和sql执行:
public interface statementhandler {
// 准备statement
statement prepare(connection connection, integer transactiontimeout) throws sqlexception;
// 参数化statement
void parameterize(statement statement) throws sqlexception;
// 执行查询
<e> list<e> query(statement statement, resulthandler resulthandler) throws sqlexception;
// 执行更新
int update(statement statement) throws sqlexception;
// 批处理
void batch(statement statement) throws sqlexception;
// 获取boundsql
boundsql getboundsql();
}
3.3 resultsethandler的作用
resultsethandler负责将resultset映射为java对象:
public interface resultsethandler {
// 处理结果集
<e> list<e> handleresultsets(statement stmt) throws sqlexception;
// 处理游标结果集
<e> cursor<e> handlecursorresultsets(statement stmt) throws sqlexception;
// 处理输出参数
void handleoutputparameters(callablestatement cs) throws sqlexception;
}
四、缓存管理机制
mybatis提供了两级缓存机制,有效提升查询性能。

4.1 一级缓存(local cache)
一级缓存是sqlsession级别的缓存,默认开启,作用域是当前sqlsession:
public class perpetualcache implements cache {
private final string id;
private final map<object, object> cache = new hashmap<>();
@override
public void putobject(object key, object value) {
cache.put(key, value);
}
@override
public object getobject(object key) {
return cache.get(key);
}
@override
public object removeobject(object key) {
return cache.remove(key);
}
@override
public void clear() {
cache.clear();
}
}
一级缓存的特点:
1、作用域:sqlsession级别 2、生命周期:与sqlsession相同,sqlsession关闭时缓存清空 3、缓存key:由mappedstatement id、参数sql、分页参数等组成 4、自动失效:执行增删改操作时,一级缓存会自动清空
4.2 二级缓存(global cache)
二级缓存是mapper级别的缓存,需要手动配置,作用域是namespace:
<!-- 在mapper xml中配置二级缓存 --> <cache eviction="lru" flushinterval="60000" size="1024" readonly="true"/>
二级缓存的特点:
1、作用域:namespace(mapper)级别 2、生命周期:应用级别,直到应用关闭 3、跨session共享:多个sqlsession可以共享 4、配置灵活:可以自定义缓存策略
4.3 缓存key的构建
cachekey由多个元素组成,确保缓存键的唯一性:
@override
public cachekey createcachekey(mappedstatement ms, object parameterobject, rowbounds rowbounds, boundsql boundsql) {
cachekey cachekey = new cachekey();
// 1. mappedstatement id
cachekey.update(ms.getid());
// 2. 分页参数
cachekey.update(rowbounds.getoffset());
cachekey.update(rowbounds.getlimit());
// 3. sql语句
cachekey.update(boundsql.getsql());
// 4. 参数值
list<parametermapping> parametermappings = boundsql.getparametermappings();
typehandlerregistry typehandlerregistry = ms.getconfiguration().gettypehandlerregistry();
for (parametermapping parametermapping : parametermappings) {
string propertyname = parametermapping.getproperty();
object value;
if (boundsql.hasadditionalparameter(propertyname)) {
value = boundsql.getadditionalparameter(propertyname);
} else if (parameterobject == null) {
value = null;
} else if (typehandlerregistry.hastypehandler(parameterobject.getclass())) {
value = parameterobject;
} else {
metaobject metaobject = configuration.newmetaobject(parameterobject);
value = metaobject.getvalue(propertyname);
}
cachekey.update(value);
}
// 5. environment id
if (configuration.getenvironment() != null) {
cachekey.update(configuration.getenvironment().getid());
}
return cachekey;
}
4.4 缓存装饰器模式
mybatis使用装饰器模式实现缓存功能的增强:
// 基础缓存
cache cache = new perpetualcache("mycache");
// 添加lru淘汰策略
cache = new lrucache(cache);
// 添加定时刷新
cache = new scheduledcache(cache);
// 添加序列化支持
cache = new serializedcache(cache);
// 添加日志记录
cache = new loggingcache(cache);
// 添加同步支持
cache = new synchronizedcache(cache);
4.5 缓存使用示例
// 一级缓存示例
sqlsession session = sqlsessionfactory.opensession();
try {
usermapper mapper = session.getmapper(usermapper.class);
// 第一次查询,访问数据库
user user1 = mapper.selectbyid(1l);
// 第二次查询,从一级缓存获取
user user2 = mapper.selectbyid(1l);
// user1 == user2,同一对象
} finally {
session.close();
}
// 二级缓存示例
sqlsession session1 = sqlsessionfactory.opensession();
sqlsession session2 = sqlsessionfactory.opensession();
try {
usermapper mapper1 = session1.getmapper(usermapper.class);
usermapper mapper2 = session2.getmapper(usermapper.class);
// session1第一次查询,访问数据库
user user1 = mapper1.selectbyid(1l);
// session1提交,将数据写入二级缓存
session1.commit();
// session2查询,从二级缓存获取
user user2 = mapper2.selectbyid(1l);
// user1 equals user2(不同对象,但值相等)
} finally {
session1.close();
session2.close();
}
五、事务管理
事务管理是数据库操作的重要组成部分,executor负责事务的创建、提交和回滚。

5.1 transaction接口
transaction是事务管理的顶层接口:
public interface transaction {
// 获取数据库连接
connection getconnection() throws sqlexception;
// 提交事务
void commit() throws sqlexception;
// 回滚事务
void rollback() throws sqlexception;
// 关闭连接
void close() throws sqlexception;
// 获取事务超时时间
integer gettimeout() throws sqlexception;
}
5.2 事务隔离级别
mybatis支持标准的事务隔离级别:
public enum isolationlevel {
none(connection.transaction_none),
read_committed(connection.transaction_read_committed),
read_uncommitted(connection.transaction_read_uncommitted),
repeatable_read(connection.transaction_repeatable_read),
serializable(connection.transaction_serializable);
}
配置示例:
<settings>
<setting name="defaulttransactionisolationlevel" value="read_committed"/>
</settings>
5.3 事务管理流程
executor的事务管理流程:
// 提交事务
@override
public void commit(boolean required) throws sqlexception {
if (closed) {
throw new executorexception("cannot commit, transaction is already closed");
}
// 1. 清空本地缓存
clearlocalcache();
// 2. 刷新批量操作
list<batchresult> batchresults = flushstatements(true);
// 3. 提交事务
if (required) {
transaction.commit();
}
return batchresults;
}
// 回滚事务
@override
public void rollback(boolean required) throws sqlexception {
if (closed) {
throw new executorexception("cannot rollback, transaction is already closed");
}
try {
// 1. 清空本地缓存
clearlocalcache();
// 2. 刷新批量操作
flushstatements(true);
// 3. 回滚事务
if (required) {
transaction.rollback();
}
} finally {
if (required) {
// 4. 关闭事务
transaction.close();
}
}
}
5.4 自动提交与手动提交
// 自动提交模式
sqlsession session = sqlsessionfactory.opensession(true);
try {
usermapper mapper = session.getmapper(usermapper.class);
mapper.insert(user);
// 无需手动提交,自动提交
} finally {
session.close();
}
// 手动提交模式(默认)
sqlsession session = sqlsessionfactory.opensession();
try {
usermapper mapper = session.getmapper(usermapper.class);
mapper.insert(user);
// 需要手动提交
session.commit();
} catch (exception e) {
// 异常时回滚
session.rollback();
throw e;
} finally {
session.close();
}
5.5 spring事务集成
在spring环境中,通常使用spring的事务管理:
@service
@transactional
public class userservice {
@autowired
private usermapper usermapper;
public void updateuser(user user) {
// spring管理事务,无需手动提交
usermapper.update(user);
}
@transactional(propagation = propagation.required)
public void transfer(long fromid, long toid, bigdecimal amount) {
// 转账操作:同一事务
usermapper.decrease(fromid, amount);
usermapper.increase(toid, amount);
}
}
六、批处理机制
批处理可以显著提升批量操作的性能。

6.1 批处理配置
使用批处理需要指定executortype:
// 创建批处理sqlsession
sqlsession session = sqlsessionfactory.opensession(executortype.batch);
try {
usermapper mapper = session.getmapper(usermapper.class);
// 批量插入
for (user user : userlist) {
mapper.insert(user);
}
// 刷新并执行批处理
session.flushstatements();
// 提交事务
session.commit();
} finally {
session.close();
}
6.2 批处理原理
batchexecutor的工作原理:
1、sql缓存:相同sql复用statement
2、参数累积:多次调用addbatch()
3、批量执行:调用executebatch()
4、结果返回:返回每条sql的执行结果
@override
public int doupdate(mappedstatement ms, object parameterobject) throws sqlexception {
configuration configuration = ms.getconfiguration();
statementhandler handler = configuration.newstatementhandler(this, ms, parameterobject,
rowbounds.default, null, null);
boundsql boundsql = ms.getboundsql(parameterobject);
string sql = boundsql.getsql();
statement stmt;
// 检查是否可以复用statement
if (sql.equals(currentsql) && ms.equals(currentstatement)) {
stmt = statementlist.get(statementlist.size() - 1);
} else {
stmt = preparestatement(handler);
statementlist.add(stmt);
batchresultlist.add(new batchresult(ms, sql, parameterobject));
currentsql = sql;
currentstatement = ms;
}
// 参数化并添加到批处理
handler.parameterize(stmt);
handler.batch(stmt);
return batch_update_return_value;
}
6.3 批处理性能优化
批处理的性能优化建议:
1、合理控制批次大小:避免一次性提交过多sql
2、使用batchexecutor:批量操作时使用批处理执行器
3、关闭自动提交:手动控制事务提交
4、合理使用flushstatements:控制批处理执行时机
// 分批处理示例
sqlsession session = sqlsessionfactory.opensession(executortype.batch);
try {
usermapper mapper = session.getmapper(usermapper.class);
int batchsize = 1000;
list<list<user>> batches = lists.partition(userlist, batchsize);
for (list<user> batch : batches) {
for (user user : batch) {
mapper.insert(user);
}
// 每批次刷新一次
session.flushstatements();
session.clearcache();
}
session.commit();
} finally {
session.close();
}
6.4 批处理返回结果
批处理返回的是每条sql影响的行数:
list<batchresult> results = session.flushstatements();
for (batchresult result : results) {
int[] updatecounts = result.getupdatecounts();
for (int count : updatecounts) {
system.out.println("影响行数: " + count);
}
}
6.5 批处理注意事项
1.statement限制:数据库对preparedstatement数量有限制 2.内存占用:大量sql会占用较多内存 3.错误处理:批处理中某条sql失败,需要特别处理 4.日志输出:批处理日志可能较多,建议适当调整日志级别
七、最佳实践
7.1 executor选择建议
| 场景 | 推荐executor | 说明 |
|---|---|---|
| 一般查询 | simple | 默认选择,每次创建新statement |
| 重复查询多 | reuse | 复用statement,减少创建开销 |
| 批量操作 | batch | 显著提升批量操作性能 |
| 启用二级缓存 | caching | 在其他executor基础上增加缓存 |
7.2 性能优化建议
1、合理使用缓存:根据业务特点选择缓存级别 2、批量操作优化:大量数据修改使用batchexecutor 3、及时清理缓存:避免缓存数据过期 4、控制事务范围:事务尽量小,减少锁竞争 5、使用连接池:避免频繁创建连接
7.3 常见问题解决
问题1:一级缓存未生效
// 问题代码 usermapper mapper = session.getmapper(usermapper.class); user user1 = mapper.selectbyid(1l); user user2 = mapper.selectbyid(1l); // user1 != user2,缓存未生效 // 原因:两次查询不在同一sqlsession // 解决:确保在同一个sqlsession中查询
问题2:二级缓存脏数据
<!-- 解决方案:设置刷新间隔 --> <cache eviction="lru" flushinterval="60000" size="1024" readonly="false"/>
问题3:批处理内存溢出
// 解决方案:分批处理
int batchsize = 1000;
for (int i = 0; i < totalsize; i += batchsize) {
list<user> batch = userlist.sublist(i, math.min(i + batchsize, totalsize));
processbatch(session, batch);
session.flushstatements();
session.clearcache();
}
八、总结
mybatis的sql执行模块是整个框架的核心执行引擎,通过精心设计的executor体系,实现了高效的sql执行、灵活的缓存管理、可靠的事务控制和强大的批处理能力。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论