# 批量插入saveBatch

Mybatis-Plus saveBatch() 批量保存失效

# 问题

在使用IService.savebatch方法批量插入数据时,观察控制台打印的Sql发现并没有像预想的一样,而是以逐条方式进行插入,插1000条数据就得10s多,正常假如批量插入应该是一条语句:

# 排查过程

先是网上搜索有没有类似的经验,看到最多的是:在JDBC连接串最后添加参数rewriteBatchedStatements=true,可以大大增加批量插入的效率,加上了发现还是一条一条插,然后又搜索为什么这个参数没用,有说数据条数要>3,这个我肯定满足,有说JDBC驱动版本问题的,都试了没用。 多方查询无果,决定从源码入手,一步一步跟进看这个saveBatch到底怎么实现的,在哪一步出了问题。

# ServiceImpl.java

  1. 入口函数,没什么好说的,重点看这个executeBatch

335414218237545.png

  1. SqlHelper.java

155574318257711.png

打断点发现,每经过一次consumer.accept(sqlSession),就打印一行insert语句出来,看看里面搞了什么鬼

  1. BatchExecutor.java

119754418250380.png

一顿Step Into后进入了这个doUpdate方法,看了一下,if体内的应该就是批量拼接sql的关键,走了几个循环发现我的代码都是从else体里走了,也就拆成了一条一条的插入语句,那他为什么不进if呢,看了下判断条件,每次进来。statement都是一个,那问题就出在sql.equals(currentSql) 上面,我比对了下第二个实体的sql和第一个实体的sql,很快就发现了问题,他们竟然不!一!样!

原因是在拼接insert语句时,如果实体的某个属性值为空,那他将不参与拼接,所以如果你的数据null值比较多且比较随机的分布在各个属性上,那生成出来的sql就会不一样,也就没法走批处理逻辑了

为了验证这个发现,我写了两段测试代码比对:

  • a. list新增三个实体,每个实体在不同的属性上设置空值
    • 打印结果(三个语句):
  • b. 还是生成三个实体,但是在相同属性上设置空值,保证数据格式一致性
    • 打印结果(一个语句):

果然,验证结论正确,实体属性为null时,会影响生成的插入sql,进而影响批量保存逻辑

# 解决方案

定位到了问题,那就也便于解决了,问题原因是生成插入sql时,对null值的处理策略造成的,查阅mybatis-plus官方文档发现,有一个配置项可以解决这个问题:

默认为NOT_NULL就是导致问题的关键,改成IGNORED就好了

再查资料发现,在@TableField注解内也可局部制定insertStrategy属性, 那解决方案就比较多样化了:

  • 全局配置insertStrategy为IGNORED
  • 为可能受影响的属性添加注解
  • 不管他那套,自己重写个批量保存方法,自己写xml拼接sql,简单粗暴(小心sql超出最大长度
mybatis-plus:
  global-config:
    db-config:
      insert-strategy: not_null # ignored,not_null,not_empty,default,never
1
2
3
4