MyBatis SQL執(zhí)行模塊的使用及解讀
本文深入剖析MyBatis的SQL執(zhí)行模塊,帶你全面理解Executor執(zhí)行器體系、緩存機(jī)制、事務(wù)管理和批處理原理。
一、MyBatis整體架構(gòu)與SQL執(zhí)行模塊
在深入SQL執(zhí)行模塊之前,我們先了解MyBatis的整體架構(gòu),以及SQL執(zhí)行模塊在其中的核心地位。

1.1 SQL執(zhí)行模塊的核心職責(zé)
SQL執(zhí)行模塊主要承擔(dān)以下核心職責(zé):
1、SQL執(zhí)行:根據(jù)MappedStatement執(zhí)行SQL語句,并返回結(jié)果 2、緩存管理:管理一級緩存和二級緩存,提高查詢性能 3、事務(wù)管理:控制數(shù)據(jù)庫事務(wù)的提交、回滾和關(guān)閉 4、批處理支持:支持批量操作,提升數(shù)據(jù)修改效率 5、Statement管理:管理JDBC Statement對象的生命周期 6、插件攔截:提供攔截點(diǎn),支持插件擴(kuò)展
1.2 Executor接口體系
Executor是SQL執(zhí)行模塊的頂層接口,定義了SQL執(zhí)行的基本方法:
public interface Executor {
// 執(zhí)行查詢(帶緩存Key)
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, CacheKey cacheKey, BoundSql boundSql);
// 執(zhí)行查詢
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler);
// 執(zhí)行更新(插入、更新、刪除)
int update(MappedStatement ms, Object parameter);
// 刷新批量操作
List<BatchResult> flushStatements();
// 提交事務(wù)
void commit(boolean required);
// 回滾事務(wù)
void rollback(boolean required);
// 創(chuàng)建CacheKey
CacheKey createCacheKey(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql);
// 判斷是否緩存
boolean isCached(MappedStatement ms, CacheKey cacheKey);
// 清空本地緩存
void clearLocalCache();
// 獲取事務(wù)
Transaction getTransaction();
// 關(guān)閉執(zhí)行器
void close(boolean forceRollback);
}
二、Executor執(zhí)行器架構(gòu)
MyBatis提供了多種Executor實(shí)現(xiàn),以適應(yīng)不同的使用場景。

2.1 Executor繼承體系
Executor采用了裝飾器模式,提供了靈活的功能擴(kuò)展:
Executor (接口) ├── BaseExecutor (抽象基類) │ ├── SimpleExecutor (簡單執(zhí)行器) │ ├── ReuseExecutor (可重用執(zhí)行器) │ └── BatchExecutor (批處理執(zhí)行器) └── CachingExecutor (緩存執(zhí)行器)
2.2 BaseExecutor抽象基類
BaseExecutor實(shí)現(xiàn)了Executor接口的大部分功能,定義了SQL執(zhí)行的基本流程:
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. 創(chuàng)建BoundSql
BoundSql boundSql = ms.getBoundSql(parameter);
// 2. 創(chuàng)建CacheKey
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 3. 執(zhí)行查詢
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;
}
// 執(zhí)行數(shù)據(jù)庫查詢
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
return list;
}
}
2.3 SimpleExecutor簡單執(zhí)行器
SimpleExecutor是最基礎(chǔ)的執(zhí)行器實(shí)現(xiàn),每次執(zhí)行SQL都會創(chuàng)建新的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. 創(chuàng)建Configuration對象
Configuration configuration = ms.getConfiguration();
// 2. 創(chuàng)建StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter,
rowBounds, resultHandler, boundSql);
// 3. 創(chuàng)建Statement
stmt = prepareStatement(handler, ms.getStatementLog());
// 4. 執(zhí)行查詢
return handler.<E>query(stmt, resultHandler);
} finally {
// 5. 關(guān)閉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可重用執(zhí)行器
ReuseExecutor會緩存Statement對象,相同SQL可以重用Statement,減少Statement創(chuàng)建開銷:
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) {
// 緩存未命中,創(chuàng)建新的Statement
stmt = prepareStatement(handler, statementLog);
statementMap.put(sql, stmt);
}
return stmt;
}
}
2.5 BatchExecutor批處理執(zhí)行器
BatchExecutor專門用于批量操作,會將多個(gè)SQL語句批量執(zhí)行:
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,復(fù)用Statement
int last = statementList.size() - 1;
stmt = statementList.get(last);
} else {
// 不同SQL,創(chuàng)建新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) {
// 執(zhí)行批處理
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緩存執(zhí)行器
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. 創(chuàng)建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執(zhí)行
List<E> list = delegate.<E>query(ms, parameter, rowBounds, resultHandler, key, boundSql);
// 3. 將結(jié)果放入二級緩存
if (cache != null) {
tcm.putObject(cache, key, list);
}
return list;
}
}
三、SQL執(zhí)行流程
SQL的執(zhí)行流程是Executor的核心工作流程。

3.1 完整執(zhí)行流程
以查詢操作為例,完整的SQL執(zhí)行流程如下:
// 1. SqlSession調(diào)用Executor
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 1.1 獲取MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
// 1.2 調(diào)用Executor執(zhí)行查詢
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執(zhí)行查詢
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) {
// 2.1 獲取BoundSql
BoundSql boundSql = ms.getBoundSql(parameter);
// 2.2 創(chuàng)建CacheKey
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 2.3 執(zhí)行查詢
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 緩存未命中,查詢數(shù)據(jù)庫
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
return list;
}
// 4. 查詢數(shù)據(jù)庫
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql) {
List<E> list;
// 4.1 占位緩存,處理循環(huán)依賴
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 4.2 執(zhí)行查詢
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 4.3 移除占位符
localCache.removeObject(key);
}
// 4.4 將結(jié)果放入一級緩存
localCache.putObject(key, list);
// 4.5 處理延遲加載
if (ms.getConfiguration().isLazyLoadingEnabled()) {
if (deferredLoads != null && !deferredLoads.isEmpty()) {
deferredLoads.clear();
}
}
return list;
}
// 5. 執(zhí)行實(shí)際查詢
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
3.2 StatementHandler的作用
StatementHandler負(fù)責(zé)Statement的創(chuàng)建、參數(shù)設(shè)置和SQL執(zhí)行:
public interface StatementHandler {
// 準(zhǔn)備Statement
Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
// 參數(shù)化Statement
void parameterize(Statement statement) throws SQLException;
// 執(zhí)行查詢
<E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
// 執(zhí)行更新
int update(Statement statement) throws SQLException;
// 批處理
void batch(Statement statement) throws SQLException;
// 獲取BoundSql
BoundSql getBoundSql();
}
3.3 ResultSetHandler的作用
ResultSetHandler負(fù)責(zé)將ResultSet映射為Java對象:
public interface ResultSetHandler {
// 處理結(jié)果集
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
// 處理游標(biāo)結(jié)果集
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
// 處理輸出參數(shù)
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
四、緩存管理機(jī)制
MyBatis提供了兩級緩存機(jī)制,有效提升查詢性能。

4.1 一級緩存(Local Cache)
一級緩存是SqlSession級別的緩存,默認(rèn)開啟,作用域是當(dāng)前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();
}
}
一級緩存的特點(diǎn):
1、作用域:SqlSession級別 2、生命周期:與SqlSession相同,SqlSession關(guān)閉時(shí)緩存清空 3、緩存Key:由MappedStatement ID、參數(shù)SQL、分頁參數(shù)等組成 4、自動失效:執(zhí)行增刪改操作時(shí),一級緩存會自動清空
4.2 二級緩存(Global Cache)
二級緩存是Mapper級別的緩存,需要手動配置,作用域是Namespace:
<!-- 在Mapper XML中配置二級緩存 --> <cache eviction="LRU" flushInterval="60000" size="1024" readOnly="true"/>
二級緩存的特點(diǎn):
1、作用域:Namespace(Mapper)級別 2、生命周期:應(yīng)用級別,直到應(yīng)用關(guān)閉 3、跨Session共享:多個(gè)SqlSession可以共享 4、配置靈活:可以自定義緩存策略
4.3 緩存Key的構(gòu)建
CacheKey由多個(gè)元素組成,確保緩存鍵的唯一性:
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
CacheKey cacheKey = new CacheKey();
// 1. MappedStatement ID
cacheKey.update(ms.getId());
// 2. 分頁參數(shù)
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
// 3. SQL語句
cacheKey.update(boundSql.getSql());
// 4. 參數(shù)值
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使用裝飾器模式實(shí)現(xiàn)緩存功能的增強(qiáng):
// 基礎(chǔ)緩存
Cache cache = new PerpetualCache("myCache");
// 添加LRU淘汰策略
cache = new LruCache(cache);
// 添加定時(shí)刷新
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);
// 第一次查詢,訪問數(shù)據(jù)庫
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第一次查詢,訪問數(shù)據(jù)庫
User user1 = mapper1.selectById(1L);
// session1提交,將數(shù)據(jù)寫入二級緩存
session1.commit();
// session2查詢,從二級緩存獲取
User user2 = mapper2.selectById(1L);
// user1 equals user2(不同對象,但值相等)
} finally {
session1.close();
session2.close();
}
五、事務(wù)管理
事務(wù)管理是數(shù)據(jù)庫操作的重要組成部分,Executor負(fù)責(zé)事務(wù)的創(chuàng)建、提交和回滾。

5.1 Transaction接口
Transaction是事務(wù)管理的頂層接口:
public interface Transaction {
// 獲取數(shù)據(jù)庫連接
Connection getConnection() throws SQLException;
// 提交事務(wù)
void commit() throws SQLException;
// 回滾事務(wù)
void rollback() throws SQLException;
// 關(guān)閉連接
void close() throws SQLException;
// 獲取事務(wù)超時(shí)時(shí)間
Integer getTimeout() throws SQLException;
}
5.2 事務(wù)隔離級別
MyBatis支持標(biāo)準(zhǔn)的事務(wù)隔離級別:
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 事務(wù)管理流程
Executor的事務(wù)管理流程:
// 提交事務(wù)
@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. 提交事務(wù)
if (required) {
transaction.commit();
}
return batchResults;
}
// 回滾事務(wù)
@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. 回滾事務(wù)
if (required) {
transaction.rollback();
}
} finally {
if (required) {
// 4. 關(guān)閉事務(wù)
transaction.close();
}
}
}
5.4 自動提交與手動提交
// 自動提交模式
SqlSession session = sqlSessionFactory.openSession(true);
try {
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.insert(user);
// 無需手動提交,自動提交
} finally {
session.close();
}
// 手動提交模式(默認(rèn))
SqlSession session = sqlSessionFactory.openSession();
try {
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.insert(user);
// 需要手動提交
session.commit();
} catch (Exception e) {
// 異常時(shí)回滾
session.rollback();
throw e;
} finally {
session.close();
}
5.5 Spring事務(wù)集成
在Spring環(huán)境中,通常使用Spring的事務(wù)管理:
@Service
@Transactional
public class UserService {
@Autowired
private UserMapper userMapper;
public void updateUser(User user) {
// Spring管理事務(wù),無需手動提交
userMapper.update(user);
}
@Transactional(propagation = Propagation.REQUIRED)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
// 轉(zhuǎn)賬操作:同一事務(wù)
userMapper.decrease(fromId, amount);
userMapper.increase(toId, amount);
}
}
六、批處理機(jī)制
批處理可以顯著提升批量操作的性能。

6.1 批處理配置
使用批處理需要指定ExecutorType:
// 創(chuàng)建批處理SqlSession
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
UserMapper mapper = session.getMapper(UserMapper.class);
// 批量插入
for (User user : userList) {
mapper.insert(user);
}
// 刷新并執(zhí)行批處理
session.flushStatements();
// 提交事務(wù)
session.commit();
} finally {
session.close();
}
6.2 批處理原理
BatchExecutor的工作原理:
1、SQL緩存:相同SQL復(fù)用Statement
2、參數(shù)累積:多次調(diào)用addBatch()
3、批量執(zhí)行:調(diào)用executeBatch()
4、結(jié)果返回:返回每條SQL的執(zhí)行結(jié)果
@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;
// 檢查是否可以復(fù)用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;
}
// 參數(shù)化并添加到批處理
handler.parameterize(stmt);
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
6.3 批處理性能優(yōu)化
批處理的性能優(yōu)化建議:
1、合理控制批次大?。罕苊庖淮涡蕴峤贿^多SQL
2、使用BatchExecutor:批量操作時(shí)使用批處理執(zhí)行器
3、關(guān)閉自動提交:手動控制事務(wù)提交
4、合理使用flushStatements:控制批處理執(zhí)行時(shí)機(jī)
// 分批處理示例
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 批處理返回結(jié)果
批處理返回的是每條SQL影響的行數(shù):
List<BatchResult> results = session.flushStatements();
for (BatchResult result : results) {
int[] updateCounts = result.getUpdateCounts();
for (int count : updateCounts) {
System.out.println("影響行數(shù): " + count);
}
}
6.5 批處理注意事項(xiàng)
1.Statement限制:數(shù)據(jù)庫對PreparedStatement數(shù)量有限制 2.內(nèi)存占用:大量SQL會占用較多內(nèi)存 3.錯(cuò)誤處理:批處理中某條SQL失敗,需要特別處理 4.日志輸出:批處理日志可能較多,建議適當(dāng)調(diào)整日志級別
七、最佳實(shí)踐
7.1 Executor選擇建議
| 場景 | 推薦Executor | 說明 |
|---|---|---|
| 一般查詢 | SIMPLE | 默認(rèn)選擇,每次創(chuàng)建新Statement |
| 重復(fù)查詢多 | REUSE | 復(fù)用Statement,減少創(chuàng)建開銷 |
| 批量操作 | BATCH | 顯著提升批量操作性能 |
| 啟用二級緩存 | CACHING | 在其他Executor基礎(chǔ)上增加緩存 |
7.2 性能優(yōu)化建議
1、合理使用緩存:根據(jù)業(yè)務(wù)特點(diǎn)選擇緩存級別 2、批量操作優(yōu)化:大量數(shù)據(jù)修改使用BatchExecutor 3、及時(shí)清理緩存:避免緩存數(shù)據(jù)過期 4、控制事務(wù)范圍:事務(wù)盡量小,減少鎖競爭 5、使用連接池:避免頻繁創(chuàng)建連接
7.3 常見問題解決
問題1:一級緩存未生效
// 問題代碼 UserMapper mapper = session.getMapper(UserMapper.class); User user1 = mapper.selectById(1L); User user2 = mapper.selectById(1L); // user1 != user2,緩存未生效 // 原因:兩次查詢不在同一SqlSession // 解決:確保在同一個(gè)SqlSession中查詢
問題2:二級緩存臟數(shù)據(jù)
<!-- 解決方案:設(shè)置刷新間隔 --> <cache eviction="LRU" flushInterval="60000" size="1024" readOnly="false"/>
問題3:批處理內(nèi)存溢出
// 解決方案:分批處理
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();
}
八、總結(jié)
MyBatis的SQL執(zhí)行模塊是整個(gè)框架的核心執(zhí)行引擎,通過精心設(shè)計(jì)的Executor體系,實(shí)現(xiàn)了高效的SQL執(zhí)行、靈活的緩存管理、可靠的事務(wù)控制和強(qiáng)大的批處理能力。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
解決Java ClassNotFoundException異常的實(shí)踐步驟
本文詳解Java中ClassNotFoundException的常見原因及解決方法,涵蓋類路徑配置、命名規(guī)范、打包問題、依賴管理、ClassLoader使用等,提供排查步驟和工具示例,感興趣的朋友跟隨小編一起學(xué)習(xí)吧2025-08-08
字節(jié)二面SpringBoot可以同時(shí)處理多少請求
這篇文章主要為大家介紹了字節(jié)二面之SpringBoot可以同時(shí)處理多少請求面試分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
SpringCloud Feign隔離與降級詳細(xì)分析
Feign是Netflix公司開發(fā)的一個(gè)聲明式的REST調(diào)用客戶端; Ribbon負(fù)載均衡、 Hystrⅸ服務(wù)熔斷是我們Spring Cloud中進(jìn)行微服務(wù)開發(fā)非?;A(chǔ)的組件,在使用的過程中我們也發(fā)現(xiàn)它們一般都是同時(shí)出現(xiàn)的,而且配置也都非常相似2022-11-11
mybatis于xml方式和注解方式實(shí)現(xiàn)多表查詢的操作方法
在數(shù)據(jù)庫中,單表的操作是最簡單的,但是在實(shí)際業(yè)務(wù)中最少也有十幾張表,并且表與表之間常常相互間聯(lián)系,本文給大家介紹mybatis于xml方式和注解方式實(shí)現(xiàn)多表查詢的操作方法,感興趣的朋友一起看看吧2023-12-12

