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

这篇文章会一步一步带你从一个新手的角度慢慢揭开批处理的神秘面纱,对于初次写Mybatis批处理的同学可能会有很大的帮助,建议收藏点赞~
处理批处理的方式有很多种,这里不分析各种方式的优劣,只是概述 ExecutorType.BATCH 这种的用法,另学艺不精,如果有错的地方,还请大佬们指出更正 。
问题原因在公司写项目的时候,有一个自动对账的需求,需要从文件中读取几万条数据插入到数据库中,后续可能跟着业务的增长,会上升到几十万,所以对于插入需要进行批处理操作,下面我们就来看看我是怎么一步一步踩坑的 。
简单了解一下批处理背后的秘密,BatchExecutor批处理是 JDBC 编程中的另一种优化手段 。JDBC 在执行 SQL 语句时,会将 SQL 语句以及实参通过网络请求的方式发送到数据库,一次执行一条 SQL 语句,一方面会减小请求包的有效负载,另一个方面会增加耗费在网络通信上的时间 。通过批处理的方式,我们就可以在 JDBC 客户端缓存多条 SQL 语句,然后在 flush 或缓存满的时候,将多条 SQL 语句打包发送到数据库执行,这样就可以有效地降低上述两方面的损耗,从而提高系统性能 。
不过,有一点需要特别注意:每次向数据库发送的 SQL 语句的条数是有上限的,如果批量执行的时候超过这个上限值,数据库就会抛出异常,拒绝执行这一批 SQL 语句,所以我们需要控制批量发送 SQL 语句的条数和频率 。

引用自《深入剖析 MyBatis 核心原理》- 杨四正 第18节
版本1-呱呱坠地废话不多说,早先时候项目的代码里就已经存在了批处理的代码,伪代码的样子大概是这样子的:
@Resourceprivate 某MApper类 mapper实例对象;private int BATCH = 1000;private void doUpdateBatch(Date accountDate, List<某实体类> data) {SqlSession batchSqlSession = null;try {if (data =https://www.isolves.com/it/cxkf/jiagou/2022-03-24/= null || data.size() == 0) {return;}batchSqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false);for (int index = 0; index < data.size(); index++) {mapper实例对象.更新/插入Method(accountDate, data.get(index).getOrderNo());if (index != 0 && index % BATCH == 0) {batchSqlSession.commit();batchSqlSession.clearCache();}}batchSqlSession.commit();} catch (Exception e) {batchSqlSession.rollback();log.error(e.getMessage(), e);} finally {if (batchSqlSession != null) {batchSqlSession.close();}}}我们先来看看上述这种写法的几种问题
你真的懂commit、clearCache、flushStatements嘛?我们先看看官网给出的解释 [图片上传失败...
Mybatis批处理踩坑,纠正网上的一些错误写法

文章插图
 
然后我们结合上述写法,它会在判断批处理条数达到1000条的时候会去手动commit,然后又手动clearCache,我们先来看看commit到底都做了一些什么,以下为调用链
@Overridepublic void commit() {commit(false);}@Overridepublic void commit(boolean force) {try {executor.commit(isCommitOrRollbackRequired(force));dirty = false;} catch (Exception e) {throw ExceptionFactory.wrapException("Error committing transaction.Cause: " + e, e);} finally {ErrorContext.instance().reset();}}private boolean isCommitOrRollbackRequired(boolean force) {// autoCommit默认为false,调用过插入、更新、删除之后的dirty值为truereturn (!autoCommit && dirty) || force;}@Overridepublic void commit(boolean required) throws SQLException {if (closed) {throw new ExecutorException("Cannot commit, transaction is already closed");}clearLocalCache();flushStatements();if (required) {transaction.commit();}}我们会发现,其实你直接调用commit的情况下,它就已经做了clearLocalCache这件事情,所以大可不必在commit后加上一句clearCache,而且clearCache是做了什么你又知道嘛?就搁这调用!!
Mybatis批处理踩坑,纠正网上的一些错误写法

文章插图
 
另外flushStatements的作用官网里有详细解释
Mybatis批处理踩坑,纠正网上的一些错误写法

文章插图
 
看到这里,我们在来看点反例,你就会觉得0.0 这都是啥跟啥啊!!!误人子弟啊,直接在百度搜一段关键字:mybatis ExecutorType.BATCH 批处理,反例如下:
Mybatis批处理踩坑,纠正网上的一些错误写法

文章插图
 
不具备通用性由于项目中用到批处理的地方肯定不止一个,那每用一次就需要CV一下,0.0 那会不会显得太菜了?能不能一劳永逸?这个时候就得用上JAVA8中的接口函数了~
版本2-初具雏形在解决完上述两个问题后,我们的代码版本来到了第2版,你以为这就对了?这就完事了?别急,我们继续往下看!


推荐阅读