Mybatis批处理踩坑,纠正网上的一些错误写法( 二 )


import lombok.extern.slf4j.Slf4j;import org.Apache.ibatis.session.ExecutorType;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.springframework.stereotype.Component;import javax.annotation.Resource;import java.util.List;import java.util.function.ToIntFunction;@Slf4j@Componentpublic class MybatisBatchUtils {/*** 每次处理1000条*/private static final int BATCH = 1000;@Resourceprivate SqlSessionFactory sqlSessionFactory;/*** 批量处理修改或者插入** @param data需要被处理的数据* @param function 自定义处理逻辑* @return int 影响的总行数*/public<T> int batchUpdateOrInsert(List<T> data, ToIntFunction<T> function) {int count = 0;SqlSession batchSqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);try {for (int index = 0; index < data.size(); index++) {count += function.applyAsInt(data.get(index));if (index != 0 && index % BATCH == 0) {batchSqlSession.flushStatements();}}batchSqlSession.commit();} catch (Exception e) {batchSqlSession.rollback();log.error(e.getMessage(), e);} finally {batchSqlSession.close();}return count;}}伪代码使用案例
@Resourceprivate 某Mapper类 mapper实例对象;batchUtils.batchUpdateOrInsert(数据集合, item -> mapper实例对象.insert方法(item));这个时候我兴高采烈的收工了,直到过了一两天,导师问我,考虑过这个业务的性能嘛,后续量大了可能每天有十多万笔数据,问我现在每天要多久,我才发现 0.0 两三万条数据插入居然要7分钟(不完全是这个问题导致这么慢,还有Oracle插入语句的原因,下面会描述),,哈哈,笑不活了,简直就是Bug制造机,我就开始思考为什么会这么慢,肯定是批处理没生效,我就思考为什么会没生效?
版本3-标准写法我们知道上面我们提到了BatchExecutor执行器,我们知道每个SqlSession都会拥有一个Executor对象,这个对象才是执行 SQL 语句的幕后黑手,我们也知道Spring跟Mybatis整合的时候使用的SqlSession是SqlSessionTemplate,默认用的是ExecutorType.SIMPLE,这个时候你通过自动注入获得的Mapper对象其实是没有开启批处理的
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Executor executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}if (cacheEnabled) {executor = new CachingExecutor(executor);}executor = (Executor) interceptorChain.pluginAll(executor);return executor;}那么我们实际上是需要通过
sqlSessionFactory.openSession(ExecutorType.BATCH)得到的sqlSession对象(此时里面的Executor是BatchExecutor)去获得一个新的Mapper对象才能生效!!!
所以我们更改一下这个通用的方法,把MapperClass也一块传递进来
public class MybatisBatchUtils {/*** 每次处理1000条*/private static final int BATCH_SIZE = 1000;@Resourceprivate SqlSessionFactory sqlSessionFactory;/*** 批量处理修改或者插入** @param data需要被处理的数据* @param mapperClassMybatis的Mapper类* @param function 自定义处理逻辑* @return int 影响的总行数*/public<T,U,R> int batchUpdateOrInsert(List<T> data, Class<U> mapperClass, BiFunction<T,U,R> function) {int i = 1;SqlSession batchSqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);try {U mapper = batchSqlSession.getMapper(mapperClass);int size = data.size();for (T element : data) {function.apply(element,mapper);if ((i % BATCH_SIZE == 0) || i == size) {batchSqlSession.flushStatements();}i++;}// 非事务环境下强制commit,事务情况下该commit相当于无效batchSqlSession.commit(!TransactionSynchronizationManager.isSynchronizationActive());} catch (Exception e) {batchSqlSession.rollback();log.error(e.getMessage(), e);} finally {batchSqlSession.close();}return i - 1;}}这里会判断是否是事务环境,不是的话会强制提交,如果是事务环境的话,这个commit设置force值是无效的,这个在前面的官网截图中有提到 。
使用案例:
batchUtils.batchUpdateOrInsert(数据集合, xxxxx.class, (item, mapper实例对象) -> mapper实例对象.insert方法(item));附:Oracle批量插入优化我们都知道Oracle主键序列生成策略跟MySQL不一样,我们需要弄一个序列生成器,这里就不详细展开描述了,然后Mybatis Generator生成的模板代码中,insert的id是这样获取的
<selectKey keyProperty="id" order="BEFORE" resultType="java.lang.Long">select XXX.nextval from dual</selectKey>


推荐阅读