Mybatis批处理

315 阅读3分钟

Mybatis中的ExecutorType中,执行SQL有三种模式:

  1. SIMPLE
  2. REUSE
  3. BATCH

这三种模式分别对应着三种执行器

以doUpdate函数为例:

  1. SimpleExecutor

    每次都会关闭statement,意味着下一次使用需要重新开启statement

  2. ReuseExecutor

    不会关闭statement,而是把statement放到缓存中,缓存的key为SQL语句,value即为对应的statement,也就是说不会每一次调用都去创建一个statement对象,而是会重复利用以前创建好的(如果SQL相同的话),这就是在很多数据连接池库中常见的PSCache概念

  3. 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

参考资料

【Mybatis源码解析第十章】详细说说Mybatis中的ExecutorType

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