背景:需要实现批量插入并且得到插入后的id。
使用for循环进行insert这里就不说了,在海量数据下其性能是最慢的。数据量小的情况下,没什么区别。
【1】savebatch(一万条数据总耗时:2478ms)
mybatisplus扩展包提供的:com.baomidou.mybatisplus.extension.service.iservice#savebatch(java.util.collection<t>)
测试代码:
@test public void testbatch1(){ list<sysfile> list=new arraylist<>(); list.add(new sysfile().setfilename("fiel1")); list.add(new sysfile().setfilename("fiel2")); list.add(new sysfile().setfilename("fiel3")); list.add(new sysfile().setfilename("fiel4")); list.add(new sysfile().setfilename("fiel5")); list.add(new sysfile().setfilename("fiel6")); fileservice.savebatch(list); system.out.println(list); }
我们分析其实现原理如下:com.baomidou.mybatisplus.extension.service.impl.serviceimpl#savebatch
@transactional(rollbackfor = exception.class) @override public boolean savebatch(collection<t> entitylist, int batchsize) { string sqlstatement = sqlstatement(sqlmethod.insert_one); int size = entitylist.size(); executebatch(sqlsession -> { int i = 1; for (t entity : entitylist) { sqlsession.insert(sqlstatement, entity); if ((i % batchsize == 0) || i == size) { sqlsession.flushstatements(); } i++; } }); return true; }
其实也就是一条条插入。
【2】集合方式foreach(一万条数据总耗时:474ms)
sysfilemapper 自定义方法batchsavefiles
public interface sysfilemapper extends basemapper<sysfile> { int batchsavefiles(list<sysfile> entitylist); }
xml实现
<insert id="batchsavefiles"> insert into tb_sys_file (file_name) values <foreach collection="list" item="item" separator=","> (#{item.filename}) </foreach> </insert>
测试代码:
@test public void testbatch2(){ list<sysfile> list=new arraylist<>(); list.add(new sysfile().setfilename("fiel1")); list.add(new sysfile().setfilename("fiel2")); list.add(new sysfile().setfilename("fiel3")); list.add(new sysfile().setfilename("fiel4")); list.add(new sysfile().setfilename("fiel5")); list.add(new sysfile().setfilename("fiel6")); filemapper.batchsavefiles(list); system.out.println(list); }
测试结果:
注意:这种方式得不到id哦!
【3】mybatis-plus提供的insertbatchsomecolumn方法(一万条数据总耗时:690ms)
这里mybatisplus版本是3.3.0。
编写mysqlinjector
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; } }
为什么这里不用下面第二行的方式呢?
methodlist.add(new insertbatchsomecolumn(i -> i.getfieldfill() != fieldfill.update)); methodlist.add(new insertbatchsomecolumn());
这两行代码分别添加了两个 insertbatchsomecolumn 方法到 methodlist 中。
第一个 insertbatchsomecolumn 方法使用了一个 lambda 表达式作为参数,该表达式用于过滤字段,只保留那些 getfieldfill 属性不是 fieldfill.update 的字段。
第二个 insertbatchsomecolumn 方法没有参数,表示不进行任何过滤,直接插入所有字段。
注入到配置类
@enabletransactionmanagement @mapperscan({"com.enodeb.mapper"}) @configuration public class mybatisplusconfig { @bean public mysqlinjector sqlinjector() { return new mysqlinjector(); } }
sysfilemapper 自定义方法
public interface sysfilemapper extends basemapper<sysfile> { int insertbatchsomecolumn(list<sysfile> entitylist);
测试代码:
@test public void testbatch3(){ list<sysfile> list=new arraylist<>(); list.add(new sysfile().setfilename("fiel1")); list.add(new sysfile().setfilename("fiel2")); list.add(new sysfile().setfilename("fiel3")); list.add(new sysfile().setfilename("fiel4")); list.add(new sysfile().setfilename("fiel5")); list.add(new sysfile().setfilename("fiel6")); filemapper.insertbatchsomecolumn(list); system.out.println(list); }
测试结果
这里不仅实现了【2】的效果,还可以得到插入后的id。
【4】假设一万条/十万条数据的情况下,执行时间是多少
策略 | 一万条 | 十万条 |
---|---|---|
方式一 | 2478ms | 20745ms |
方式二 | 474ms | 2904ms |
方式三 | 690ms | 8339ms |
① 方式一
@test public void testbatch1(){ long start=system.currenttimemillis(); list<sysfile> list=new arraylist<>(); sysfile sysfile; for(int i=0;i<10000;i++){ sysfile=new sysfile(); sysfile.setfilename("file"+i); list.add(sysfile); } fileservice.savebatch(list); long end=system.currenttimemillis(); system.out.println("一万条数据总耗时:"+(end-start)+"ms"); }
一万条数据总耗时:2478ms
十万条数据总耗时:20745ms
② 方式二
@test public void testbatch2(){ long start=system.currenttimemillis(); list<sysfile> list=new arraylist<>(); sysfile sysfile; for(int i=0;i<10000;i++){ sysfile=new sysfile(); sysfile.setfilename("file"+i); list.add(sysfile); } filemapper.batchsavefiles(list); long end=system.currenttimemillis(); system.out.println("一万条数据总耗时:"+(end-start)+"ms"); }
一万条数据总耗时:474ms
十万条数据总耗时:2904ms
③ 方式三
@test public void testbatch3(){ long start=system.currenttimemillis(); list<sysfile> list=new arraylist<>(); sysfile sysfile; for(int i=0;i<10000;i++){ sysfile=new sysfile(); sysfile.setfilename("file"+i); list.add(sysfile); } filemapper.insertbatchsomecolumn(list); long end=system.currenttimemillis(); system.out.println("一万条数据总耗时:"+(end-start)+"ms"); }
一万条数据总耗时:690ms
十万条数据总耗时:8339ms
【5】百万条数据的情况下进行优化
方式二、方式三都是拼接为一条sql,也就说有多少直接全部一次性插入,这就可能会导致最后的 sql 拼接语句特别长,超出了mysql 的限制。
这是什么意思呢?以mysql为例,我们是需要考虑 max_allowed_packet 这个属性配置大小。其决定了你最大可以单次发送包的大小,这里可以修改为64m也就是 67108864。
但是这个不是最优解,最优解应该是控制每次插入的数量,比如一万条插入一次。
@test public void testbatch4(){ list<sysfile> list=new arraylist<>(); sysfile sysfile; for(int i=0;i<100000;i++){ sysfile=new sysfile(); sysfile.setfilename("file"+i); list.add(sysfile); } //设置每批次插入多少条数据 int batchsize=10000; int count = (list.size() + batchsize - 1) / batchsize; // 计算总批次数量,确保最后一个批次也能处理 //保存单批提交的数据集合 list<sysfile> onebatchlist = new arraylist<>(batchsize); // 预分配容量 for (int i = 0; i < count; i++) { int startindex = i * batchsize; int endindex = math.min(startindex + batchsize, list.size()); onebatchlist.addall(list.sublist(startindex, endindex)); filemapper.insertbatchsomecolumn(onebatchlist); onebatchlist.clear(); // 清空集合以备下次循环使用 } }
【tips】
为了确保批量插入的高效性,还需要进行一些配置和优化。例如,在application.yml中配置数据库连接时,可以开启mysql的批处理模式
【rewritebatchedstatements=true】:
spring: datasource: url: jdbc:mysql://127.0.0.1:3306/testbtach?useunicode=true&characterencoding=utf-8&servertimezone=asia/shanghai&rewritebatchedstatements=true username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.driver
此外还可以考虑使用jdbctemplate.batchupdate、spring batch来实现(这两种未测试)。
到此这篇关于springboot整合mybatisplus实现批量插入并获取id详解的文章就介绍到这了,更多相关springboot整合mybatisplus插入id内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论