Mybatis中的ExecutorType中,执行SQL有三种模式:
- SIMPLE
- REUSE
- BATCH
这三种模式分别对应着三种执行器
以doUpdate函数为例:
-
SimpleExecutor
每次都会关闭statement,意味着下一次使用需要重新开启statement
-
ReuseExecutor
不会关闭statement,而是把statement放到缓存中,缓存的key为SQL语句,value即为对应的statement,也就是说不会每一次调用都去创建一个statement对象,而是会重复利用以前创建好的(如果SQL相同的话),这就是在很多数据连接池库中常见的PSCache概念
-
BatchExecutor
不会像前面两个那样执行然后返回行数,而是每次执行将statement预存到有序集合,官方说明这个executor是用于执行存储过程和批量操作的,因此这个方法是循环或者多次执行构建一个存储过程或批处理过程
性能对比
SimpleExecutor比ResueExecutor性能要差,因为SimpleExecutor没有做PSCache。为什么做了PSCache性能就会高呢?因为当SQL越复杂占位符越多时,预编译的时间越长,创建一个PreparedStatement对象的时间也就越长;
猜想中BatchExecutor比ReuseExecutor功能强大性能高,实际上并非如此,BatchExecutor是没有做PSCache的。
BatchExecutor与SimpleExecutor和ReuseExecutor还有一个区别就是,BatchExecutor的事务是没法自动提交的,因为BatchExecutor只有在调用了SqlSession的commit方法的时候,才回去执行executeBatch方法。
示例
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import javax.annotation.Resource;
import java.util.List;
import java.util.function.BiFunction;
/**
* Mybatis批量更新工具
* 需要连接数据库时配置允许批量更新:rewriteBatchedStatements=true
* @author cc
* @date 2022-11-25 18:40
*/
@Component
public class MybatisBatchUtils {
/**
* 批量更新的数量
*/
private static final int BATCH_COUNT = 1000;
@Resource
private SqlSessionFactory sqlSessionFactory;
/**
* 批量修改数据:插入或更新
* 1. 出现错误时退出,但前面已保存的数据不能回滚
* 2. 出现错误时继续
*/
public <T, U, R> int batchUpdateOrInsert(List<T> dataList, Class<U> mapperClass, BiFunction<T, U, R> function) {
return batchUpdateOrInsert(dataList, mapperClass, function, false);
}
/**
* 批量处理修改或插入
* @param dataList 需要被处理的数据
* @param mapperClass mapper类,因为通过自动注入的mapper对象用的是SimpleExecutor,是没有开启批处理的
* @param function 自定义处理逻辑
* @param stopIfError 出现错误时停止
* @return 影响的总行数
*/
public <T, U, R> int batchUpdateOrInsert(List<T> dataList, Class<U> mapperClass, BiFunction<T, U, R> function, boolean stopIfError) {
if (dataList == null || dataList.size() == 0) {
return 0;
}
int i = 1;
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
U mapper = session.getMapper(mapperClass);
for (T item : dataList) {
function.apply(item, mapper);
// 达到一定量时提交
if (i != 0 && (i % BATCH_COUNT == 0)) {
if (stopIfError) {
session.flushStatements();
} else {
try {
session.flushStatements();
} catch (Exception e) {
e.printStackTrace();
}
}
}
i++;
}
// 非事务环境下强制commit,事务环境下该commit相当于无效
session.commit(!TransactionSynchronizationManager.isSynchronizationActive());
session.close();
return dataList.size();
}
}
拓展
关于批量插入数据冲突的问题,MySQL中支持当数据重复时更新数据的操作,SQL语句为:
INSERT INTO user VALUES(56730,'2222',1,1) ON DUPLICATE KEY UPDATE id=56730, name='cc', age=2, price=11