1 前言
“不要用 mybatis-plus 的批量插入,它其实也是遍历插入,性能很差的”。真的吗?他们的立场如下:
- 遍历插入,反复创建。这是一个重量级操作,所以性能差。这里不用看源码也知道,因为这个和mybatis-plus没关系,而且我们现在使用SpringBoot,一般也用它的JDBC启动依赖。连接和连接池不是本文重点,总之这观点纯属无稽之谈,和不懂技术的领导说话一个德行
- 一条 insert 就一次网络IO,数量多了,这是个很可观且没必要的开销,所以性能差
2 show me源码!
对第2个观点,结合源码来看吧!
2.1 环境
① mybatis-plus 3.5.3.1
pom.xml:
② application.yml
Spring:# 使用默认的连接池库 datasource:url: "*****"username: "****"password: "****"
Service使用之处:
2.2 进入saveBatch
com.baomidou.mybatisplus.extension.service.IService#saveBatch(java.util.Collection)
会给我们这个批量操作开启事务(若是期望插入一条就成功一条,该批量方法就不适用);
且有限制提交数量,默认1000。
来到ServiceImpl实现类:
可见,MP批量插入是一条条插入的,但是这个一次次的遍历是真的发送给MySQL了吗?。只要记得这里有一个钩子,后面会回调回来执行。
/*** 获取mapperStatementId** @param sqlMethod 方法名* @return 命名id* @since 3.4.0*/
protected String getSqlStatement(SqlMethod sqlMethod) {return SqlHelper.getSqlStatement(mapperClass, sqlMethod);
}/*** 执行批量操作** @param list 数据集合* @param batchSize 批量大小* @param consumer 执行方法* @param <E> 泛型* @return 操作结果* @since 3.3.1*/
protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {return SqlHelper.executeBatch(this.entityClass, this.log, list, batchSize, consumer);
}
3 SqlHelper#executeBatch(Class<?>, Log, Collection, int, BiConsumer<SqlSession,E>)
com.baomidou.mybatisplus.extension.toolkit.SqlHelper#executeBatch(java.lang.Class<?>, org.apache.ibatis.logging.Log, java.util.Collection, int, java.util.function.BiConsumer<org.apache.ibatis.session.SqlSession,E>)
/*** 执行批量操作** @param entityClass 实体类* @param log 日志对象* @param list 数据集合* @param batchSize 批次大小* @param consumer consumer* @param <E> T* @return 操作结果* @since 3.4.0*/
public static <E> boolean executeBatch(Class<?> entityClass, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {Assert.isFalse(batchSize < 1, "batchSize must not be less than one");return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, sqlSession -> {int size = list.size();int idxLimit = Math.min(batchSize, size);int i = 1;for (E element : list) {consumer.accept(sqlSession, element);if (i == idxLimit) {sqlSession.flushStatements();idxLimit = Math.min(idxLimit + batchSize, size);}i++;}});
}
sqlSession从哪来的?看executeBatch。
4 SqlHelper#executeBatch(Class<?> entityClass, Log log, Consumer consumer)
com.baomidou.mybatisplus.extension.toolkit.SqlHelper#executeBatch(java.lang.Class<?>, org.apache.ibatis.logging.Log, java.util.function.Consumer<org.apache.ibatis.session.SqlSession>)
public static boolean executeBatch(Class<?> entityClass, Log log, Consumer<SqlSession> consumer) {SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);try {consumer.accept(sqlSession);//非事务情况下,强制commit。sqlSession.commit(!transaction);}
}
结合前面代码,这里是到了1000(默认1000,并且我批量保存的list超过1000),就会开启会话,将内存的SQL全部刷到MySQL,然后回去继续遍历。
总结
MP批量插入虽然是遍历插入,但不是一个insert就一次IO,而是打包了一次发送一批,所以性能没太大问题。但不是鼓励大家都用这批量插入,实际工作有更多要求,有时简单的批量插入没法满足。还是看实际情况决定。
扩展
如果使用mybatis-plus 3.4+ 版本,并且连接的是 MySQL 8.0 或更高版本的数据库,那么 mybatis-plus将会自动利用MySQL 8.0 的原生批量插入功能来执行批量插入操作。
具体实现的关键是在mybatis-plus的底层使用了mybatis-plus的批量新增方法时,mybatis-plus会将待插入的对象列表传递给底层的。
注意确保以下条件满足才能利用:
使用 MySQL 8.0 或更高版本的数据库
使用兼容 MySQL 8.0 的 JDBC 驱动程序(如 mysql-connector-java 版本 8.0 或更高)
使用 mybatis-plus 3.4+ 版本
参考:https://www.jianshu.com/p/a387879ccb97