最近使用mybatis-plus出现了一个小bug,版本是mybatis-plus:3.0.1
使用IService接口内置的updateBatchById时报错了,原因是没有找到对应的xxxx.Mapper.updateById
所以翻了一下BaseMapper的接口也确实没有该同名方法的声明,觉得挺不可思议的,一个知名度相当高的Mybatis加强版组件,居然有这样低级的错误?但是翻阅时,发现内置写了SqlMethod.UPDATE_BY_ID的SQL脚本,出于好奇,了解一下Mybatis-plus的实现方案。
于是到Gitee下载了Mybatis-plus的源码稍微翻了一下(版本3.4.1),装配Mapper思路大概如下:
MybatisMapperRegistry.addMapper()
->MybatisMapperAnnotationBuilder.parse()
->AbstractSqlInjector.inspectInject()
// 很重要的一段代码:List<AbstractMethod> methodList = this.getMethodList(mapperClass)
->AbstractMethod.addMappedStatement()
DefaultSqlInjector返回了BaseMapper方法下所有的声明了的方法对应的MappedStatement,通过injectMappedStatement方法实现注入Mybatis最核心Configuration类的mappedStatements缓存中。
由于用的是批量更新,顺道看看批量更新有没有做一些优化。
截了一部分3.0.1的代码(3.4.1的代码实现稍微复杂一点,但是本质是相同的):
public boolean updateBatchById(Collection<T> entityList, int batchSize) {
if (CollectionUtils.isEmpty(entityList)) {
throw new IllegalArgumentException("Error: entityList must not be empty");
} else {
try {
SqlSession batchSqlSession = this.sqlSessionBatch();
Throwable var4 = null;
try {
int i = 0;
String sqlStatement = this.sqlStatement(SqlMethod.UPDATE_BY_ID);
for(Iterator var7 = entityList.iterator(); var7.hasNext(); ++i) {
T anEntityList = var7.next();
ParamMap<T> param = new ParamMap();
param.put("et", anEntityList);
batchSqlSession.update(sqlStatement, param);
if (i >= 1 && i % batchSize == 0) {
batchSqlSession.flushStatements();
}
}
batchSqlSession.flushStatements();
注意看sqlSessionBatch()这个方法,最终决定了Executor的类型为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)) {
// 如果是同一个sql,使用的是同一个MappedStatment,则使用同一个Statement
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());
handler.parameterize(stmt); //fix Issues 322
currentSql = sql;
currentStatement = ms;
statementList.add(stmt);
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
// handler.parameterize(stmt);
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
查阅SimpleExecutor可以看到每次执行完SQL都会将Statement关闭(源码如下),主要目的是清理该Statement所占据的数据库资源,比如游标。所以如果在批量操作中使用同一个Statement来执行SQL,然后定期执行多次后再通过flushStatements操作达到刷新的目的,可以避免频繁的创建,销毁Statement所带来的高额开销。
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);
}
}
网友评论