Mybatis Plus 批量插入这样操作提升性能
ztj100 2025-01-07 17:23 49 浏览 0 评论
使用的mybatisplus的批量插入方法:saveBatch(),之前就看到过网上都在说在jdbc的url路径上加上rewriteBatchedStatements=true 参数mysql底层才能开启真正的批量插入模式。
保证5.1.13以上版本的驱动,才能实现高性能的批量插入。MySQL JDBC驱动在默认情况下会无视executeBatch()语句,把我们期望批量执行的一组sql语句拆散,一条一条地发给MySQL数据库,批量插入实际上是单条插入,直接造成较低的性能。只有把rewriteBatchedStatements参数置为true, 驱动才会帮你批量执行SQL。另外这个选项对INSERT/UPDATE/DELETE都有效。
目前我的数据表目前是没有建立索引的,即使是在1000来w的数据量下进行1500条的批量插入也不可能消耗20来秒吧,于是矛盾转移到saveBatch方法,使用版本:
查看源码:
public boolean saveBatch(Collection<T> entityList, int batchSize) { String sqlStatement = this.getSqlStatement(SqlMethod.INSERT_ONE); return this.executeBatch(entityList, batchSize, (sqlSession, entity) -> { sqlSession.insert(sqlStatement, entity); }); }
protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) { return SqlHelper.executeBatch(this.entityClass, this.log, list, batchSize, consumer); }
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", new Object[0]); return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, (sqlSession) -> { int size = list.size(); int i = 1; for(Iterator var6 = list.iterator(); var6.hasNext(); ++i) { E element = var6.next(); consumer.accept(sqlSession, element); if (i % batchSize == 0 || i == size) { sqlSession.flushStatements(); } } });}
最终来到了executeBatch()方法,可以看到这很明显是在一条一条循环插入,通过sqlSession.flushStatements()将一个个单条插入的insert语句分批次进行提交,而且是同一个sqlSession,这相比遍历集合循环insert来说有一定的性能提升,但是这并不是sql层面真正的批量插入。
通过查阅相关文档后,发现mybatisPlus提供了sql注入器,我们可以自定义方法来满足业务的实际开发需求。
sql注入器官网
https://baomidou.com/pages/42ea4a/
sql注入器官方示例
https://gitee.com/baomidou/mybatis-plus-samples/tree/master/mybatis-plus-sample-deluxe
在mybtisPlus的核心包下提供的默认可注入方法有这些:
在扩展包下,mybatisPlus还为我们提供了可扩展的可注入方法:
- AlwaysUpdateSomeColumnById:根据Id更新每一个字段,全量更新不忽略null字段,解决mybatis-plus中updateById默认会自动忽略实体中null值字段不去更新的问题;
- InsertBatchSomeColumn:真实批量插入,通过单SQL的insert语句实现批量插入;
- Upsert:更新or插入,根据唯一约束判断是执行更新还是删除,相当于提供insert on duplicate key update支持。
可以发现mybatisPlus已经提供好了InsertBatchSomeColumn的方法,我们只需要把这个方法添加进我们的sql注入器即可。
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE; SqlMethod sqlMethod = SqlMethod.INSERT_ONE; List<TableFieldInfo> fieldList = tableInfo.getFieldList(); String insertSqlColumn = tableInfo.getKeyInsertSqlColumn(true, false) + this.filterTableFieldInfo(fieldList, this.predicate, TableFieldInfo::getInsertSqlColumn, ""); //------------------------------------拼接批量插入语句---------------------------------------- String columnScript = "(" + insertSqlColumn.substring(0, insertSqlColumn.length() - 1) + ")"; String insertSqlProperty = tableInfo.getKeyInsertSqlProperty(true, "et.", false) + this.filterTableFieldInfo(fieldList, this.predicate, (i) -> { return i.getInsertSqlProperty("et."); }, ""); insertSqlProperty = "(" + insertSqlProperty.substring(0, insertSqlProperty.length() - 1) + ")"; String valuesScript = SqlScriptUtils.convertForeach(insertSqlProperty, "list", (String)null, "et", ","); //------------------------------------------------------------------------------------------ String keyProperty = null; String keyColumn = null; if (tableInfo.havePK()) { if (tableInfo.getIdType() == IdType.AUTO) { keyGenerator = Jdbc3KeyGenerator.INSTANCE; keyProperty = tableInfo.getKeyProperty(); keyColumn = tableInfo.getKeyColumn(); } else if (null != tableInfo.getKeySequence()) { keyGenerator = TableInfoHelper.genKeyGenerator(this.getMethod(sqlMethod), tableInfo, this.builderAssistant); keyProperty = tableInfo.getKeyProperty(); keyColumn = tableInfo.getKeyColumn(); } } String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript); SqlSource sqlSource = this.languageDriver.createSqlSource(this.configuration, sql, modelClass); return this.addInsertMappedStatement(mapperClass, modelClass, this.getMethod(sqlMethod), sqlSource, (KeyGenerator)keyGenerator, keyProperty, keyColumn);}
接下来就通过SQL注入器实现真正的批量插入
默认的sql注入器
public class DefaultSqlInjector extends AbstractSqlInjector { public DefaultSqlInjector() { } public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) { if (tableInfo.havePK()) { return (List)Stream.of(new Insert(), new Delete(), new DeleteByMap(), new DeleteById(), new DeleteBatchByIds(), new Update(), new UpdateById(), new SelectById(), new SelectBatchByIds(), new SelectByMap(), new SelectCount(), new SelectMaps(), new SelectMapsPage(), new SelectObjs(), new SelectList(), new SelectPage()).collect(Collectors.toList()); } else { this.logger.warn(String.format("%s ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.", tableInfo.getEntityType())); return (List)Stream.of(new Insert(), new Delete(), new DeleteByMap(), new Update(), new SelectByMap(), new SelectCount(), new SelectMaps(), new SelectMapsPage(), new SelectObjs(), new SelectList(), new SelectPage()).collect(Collectors.toList()); } }}
继承DefaultSqlInjector自定义sql注入器
/** * @author zhmsky * @date 2022/8/15 15:13 */public class MySqlInjector extends DefaultSqlInjector { @Override public List<AbstractMethod> getMethodList(Class<?> mapperClass) { List<AbstractMethod> methodList = super.getMethodList(mapperClass); //更新时自动填充的字段,不用插入值 methodList.add(new InsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.UPDATE)); return methodList; }}
将自定义的sql注入器注入到Mybatis容器中
/** * @author zhmsky * @date 2022/8/15 15:15 */@Configurationpublic class MybatisPlusConfig { @Bean public MySqlInjector sqlInjector() { return new MySqlInjector(); }}
继承BaseMapper添加自定义方法
/** * @author zhmsky * @date 2022/8/15 15:17 */public interface CommonMapper<T> extends BaseMapper<T> { /** * 真正的批量插入 * @param entityList * @return */ int insertBatchSomeColumn(List<T> entityList);}
对应的mapper层接口继承上面自定义的mapper
/* * @author zhmsky * @since 2021-12-01 */@Mapperpublic interface UserMapper extends CommonMapper<User> {}
最后直接调用UserMapper的insertBatchSomeColumn()方法即可实现真正的批量插入。
@Testvoid contextLoads() { for (int i = 0; i < 5; i++) { User user = new User(); user.setAge(10); user.setUsername("zhmsky"); user.setEmail("21575559@qq.com"); userList.add(user); } long l = System.currentTimeMillis(); userMapper.insertBatchSomeColumn(userList); long l1 = System.currentTimeMillis(); System.out.println("-------------------:"+(l1-l)); userList.clear();}
查看日志输出信息,观察执行的sql语句;
发现这才是真正意义上的sql层面的批量插入。
但是,到这里并没有结束,mybatisPlus官方提供的insertBatchSomeColumn方法不支持分批插入,也就是有多少直接全部一次性插入,这就可能会导致最后的sql拼接语句特别长,超出了mysql的限制,于是我们还要实现一个类似于saveBatch的分批的批量插入方法。
添加分批插入
模仿原来的saveBatch方法:
* @author zhmsky * @since 2021-12-01 */@Servicepublic class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { @Override @Transactional(rollbackFor = {Exception.class}) public boolean saveBatch(Collection<User> entityList, int batchSize) { try { int size = entityList.size(); int idxLimit = Math.min(batchSize, size); int i = 1; //保存单批提交的数据集合 List<User> oneBatchList = new ArrayList<>(); for (Iterator<User> var7 = entityList.iterator(); var7.hasNext(); ++i) { User element = var7.next(); oneBatchList.add(element); if (i == idxLimit) { baseMapper.insertBatchSomeColumn(oneBatchList); //每次提交后需要清空集合数据 oneBatchList.clear(); idxLimit = Math.min(idxLimit + batchSize, size); } } } catch (Exception e) { log.error("saveBatch fail", e); return false; } return true; }}
测试:
@Testvoid contextLoads() { for (int i = 0; i < 20; i++) { User user = new User(); user.setAge(10); user.setUsername("zhmsky"); user.setEmail("21575559@qq.com"); userList.add(user); } long l = System.currentTimeMillis(); userService.saveBatch(userList,10); long l1 = System.currentTimeMillis(); System.out.println("-------------------:"+(l1-l)); userList.clear();}
输出结果:
分批插入已满足,到此收工结束了。
接下来最重要的测试下性能
当前数据表的数据量在100w多条,在此基础上分别拿原始的saveBatch(假的批量插入)和 insertBatchSomeColumn(真正的批量插入)进行性能对比----(jdbc均开启rewriteBatchedStatements):
原来的假的批量插入:
@Test void insert(){ for (int i = 0; i < 50000; i++) { User user = new User();
自定义的insertBatchSomeColumn:
@Testvoid contextLoads() { for (int i = 0; i < 50000; i++) { User user = new User
分批插入5w条数据,自定义的真正意义上的批量插入耗时减少了3秒左右,用insertBatchSomeColum分批插入1500条数据耗时650毫秒,这速度已经挺快了
- 上一篇:Java中的SSM框架详解
- 下一篇:MyBatis3.5.11-从入门到高阶
相关推荐
- 离谱!写了5年Vue,还不会自动化测试?
-
前言大家好,我是倔强青铜三。是一名热情的软件工程师,我热衷于分享和传播IT技术,致力于通过我的知识和技能推动技术交流与创新,欢迎关注我,微信公众号:倔强青铜三。Playwright是一个功能强大的端到...
- package.json 与 package-lock.json 的关系
-
模块化开发在前端越来越流行,使用node和npm可以很方便的下载管理项目所需的依赖模块。package.json用来描述项目及项目所依赖的模块信息。那package-lock.json和...
- Github 标星35k 的 SpringBoot整合acvtiviti开源分享,看完献上膝盖
-
前言activiti是目前比较流行的工作流框架,但是activiti学起来还是费劲,还是有点难度的,如何整合在线编辑器,如何和业务表单绑定,如何和系统权限绑定,这些问题都是要考虑到的,不是说纯粹的把a...
- Vue3 + TypeScript 前端研发模板仓库
-
我们把这个Vue3+TypeScript前端研发模板仓库的初始化脚本一次性补全到可直接运行的状态,包括:完整的目录结构所有配置文件研发规范文档示例功能模块(ExampleFeature)...
- Vue 2迁移Vue 3:从响应式到性能优化
-
小伙伴们注意啦!Vue2已经在2023年底正式停止维护,再不升级就要面临安全漏洞没人管的风险啦!而且Vue3带来的性能提升可不是一点点——渲染速度快40%,内存占用少一半,更新速度直接翻倍!还在...
- VUE学习笔记:声明式渲染详解,对比WEB与VUE
-
声明式渲染是指使用简洁的模板语法,声明式的方式将数据渲染进DOM系统。声明式是相对于编程式而言,声明式是面向对象的,告诉框架做什么,具体操作由框架完成。编程式是面向过程思想,需要手动编写代码完成具...
- 苏州web前端培训班, 苏州哪里有web前端工程师培训
-
前端+HTML5德学习内容:第一阶段:前端页面重构:PC端网站布局、HTML5+CSS3基础项目、WebAPP页面布局;第二阶段:高级程序设计:原生交互功能开发、面向对象开发与ES5/ES6、工具库...
- 跟我一起开发微信小程序——扩展组件的代码提示补全
-
用户自定义代码块步骤:1.HBuilderX中工具栏:工具-代码块设置-vue代码块2.通过“1”步骤打开设置文件...
- JimuReport 积木报表 v1.9.3发布,免费可视化报表
-
项目介绍积木报表JimuReport,是一款免费的数据可视化报表,含报表、大屏和仪表盘,像搭建积木一样完全在线设计!功能涵盖:数据报表、打印设计、图表报表、门户设计、大屏设计等!...
- 软开企服开源的无忧企业文档(V2.1.3)产品说明书
-
目录1....
- 一款面向 AI 的下一代富文本编辑器,已开源
-
简介AiEditor是一个面向AI的下一代富文本编辑器。开箱即用、支持所有前端框架、支持Markdown书写模式什么是AiEditor?AiEditor是一个面向AI的下一代富文本编辑...
- 玩转Markdown(2)——抽象语法树的提取与操纵
-
上一篇玩转Markdown——数据的分离存储与组件的原生渲染发布,转眼已经鸽了大半年了。最近在操纵mdast生成md文件的时候,心血来潮,把玩转Markdown(2)给补上了。...
- DeepseekR1+ollama+dify1.0.0搭建企业/个人知识库(入门避坑版)
-
找了网上的视频和相关文档看了之后,可能由于版本不对或文档格式不对,很容易走弯路,看完这一章,可以让你少踩三天的坑。步骤和注意事项我一一列出来:1,前提条件是在你的电脑上已配置好ollama,dify1...
- 升级JDK17的理由,核心是降低GC时间
-
升级前后对比升级方法...
- 一个vsCode格式化插件_vscode格式化插件缩进量
-
ESlint...
你 发表评论:
欢迎- 一周热门
-
-
MySQL中这14个小玩意,让人眼前一亮!
-
旗舰机新标杆 OPPO Find X2系列正式发布 售价5499元起
-
【VueTorrent】一款吊炸天的qBittorrent主题,人人都可用
-
面试官:使用int类型做加减操作,是线程安全吗
-
C++编程知识:ToString()字符串转换你用正确了吗?
-
【Spring Boot】WebSocket 的 6 种集成方式
-
PyTorch 深度学习实战(26):多目标强化学习Multi-Objective RL
-
pytorch中的 scatter_()函数使用和详解
-
与 Java 17 相比,Java 21 究竟有多快?
-
基于TensorRT_LLM的大模型推理加速与OpenAI兼容服务优化
-
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)