尊重个人劳动成果,转载请注明出处: https://blog.csdn.net/walson_z/article/details/109909852
一、概述
本文主要对BatchExecutor两个主要的方法doUpdate和doFlushStatements的源码进行解析,前者所有涉及数据变动的Mapper方法(insert、update、delete)都会最终调用到,主要作用是将连续执行且sql相同的Statement做分组保存,方便后面批量执行;后者会在提交事务的时候会被调用,主要作用是将前面分组保存的Statement进行批量执行。
二、BatchExecutor.doUpdate()
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
final Configuration configuration = ms.getConfiguration();
final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
final BoundSql boundSql = handler.getBoundSql();
final String sql = boundSql.getSql();
final Statement stmt;
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
int last = statementList.size() - 1;
stmt = statementList.get(last);
applyTransactionTimeout(stmt);
handler.parameterize(stmt);// fix Issues 322
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
} else {
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
//Statement参数化。参数化后下面调用的handler.batch(stmt)将前面参数化的statement添加到批量处理中
handler.parameterize(stmt); // fix Issues 322
currentSql = sql;
currentStatement = ms;
statementList.add(stmt);
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
重点关注7~23行:判断本次执行的sql以及由它生成的MapperedStatement是否与上一次的相同,如果不相同则创建Statement,参数化Statement,并将Statement保存在一个列表中;另外创建一个与Statement对应的BatchResult,同样放在一个列表中。如果相同,则取出列表中最后一个Statement进行参数化(后续调用addBatch()添加到批量处理);另外取出BatchResult,将本条sql的参数设置进去
三、BatchExecutor.doFlushStatements()
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
try {
List<BatchResult> results = new ArrayList<>();
if (isRollback) {
return Collections.emptyList();
}
//逐个取出之前分组保存的Statement进行批量执行
for (int i = 0, n = statementList.size(); i < n; i++) {
Statement stmt = statementList.get(i);
applyTransactionTimeout(stmt);
BatchResult batchResult = batchResultList.get(i);
try {
batchResult.setUpdateCounts(stmt.executeBatch());
MappedStatement ms = batchResult.getMappedStatement();
List<Object> parameterObjects = batchResult.getParameterObjects();
KeyGenerator keyGenerator = ms.getKeyGenerator();
if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {
Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
} else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { //issue #141
for (Object parameter : parameterObjects) {
keyGenerator.processAfter(this, ms, stmt, parameter);
}
}
// Close statement to close cursor #1109
closeStatement(stmt);
} catch (BatchUpdateException e) {
StringBuilder message = new StringBuilder();
message.append(batchResult.getMappedStatement().getId())
.append(" (batch index #")
.append(i + 1)
.append(")")
.append(" failed.");
if (i > 0) {
message.append(" ")
.append(i)
.append(" prior sub executor(s) completed successfully, but will be rolled back.");
}
throw new BatchExecutorException(message.toString(), e, results, batchResult);
}
results.add(batchResult);
}
return results;
} finally {
for (Statement stmt : statementList) {
closeStatement(stmt);
}
currentSql = null;
statementList.clear();
batchResultList.clear();
}
}
重点关注8~24行:逐个取出之前保存的列表中的每个Statement,对其调用executeBatch(),并将返回值设置到BatchResult.updateCount字段。如果是sql是insert,则将插入时自动生成的主键填充回BatchResult.parameterObjects中,某些业务可能需要用到这些自动生成的主键做后续处理,这时候可以在插入后从返回的BatchResult中获取。
四、总结
本文主要解析了BatchExecutor源码中如何实现批量处理,值得注意的是,如果使用Spring集成Mybatis,SqlSessionTemplate会判断当前是否存在事务,不存在的话,每次执行sql都会进行提交,这样的话即使ExecutorType设置为BATCH,批量操作也不会生效。最近在学习Mybatis框架,关于事务和批处理的关系,原生Mybatis、Mybatis-Spring和底层JDBC的事务的关系,还有许多问题有待梳理,后续再写文章讨论。
文章若有纰漏、逻辑不通的地方,还请大家批评指出!