一、前言这几天在开发公司业务的时候,遇到了一个场景,需要向多个表中插入大量数据数据库,所以想出了这篇文章:Mybatis批量插入数据使用注意事项,使用函数式编程对批量数据插入方式进行简单封装。对于包括我在内的大部分Java后端开发小伙伴来说,在平时的CURD开发工作中,难免会用到Mybatis工具来操作数据库。在SpringBoot项目中,引入Mybatis后,可以使用mapper注入实现增删改查。比如要添加一条数据,可以在mapper.xml文件中这样写:insertintomassive_data_insert_test(value1,value2)values(#{value1},#{value2})然后调用mapper.insertOneItem(insertItem);在服务层。如果你想添加多条数据,如果你刚学Java,你可能会这样写:for(inti=0;i标签,可以帮助我们拼接出上面的SQL语句。我们在mapper.xml中写入如下内容:insertintomassive_data_insert_test(value1,value2)values(#{item.value1},#{item.value2})这样我们只需要调用一次mapper中的方法就可以达到上面for循环代码的效果。而实际上,SQL在执行过程中只执行一次。这条动态SQL语句的作用是将传入参数的内容拼接成插入语句的SQL(预处理技术)。这种方法明显优于for循环的原始实现。2、当批量插入的数据量达到数万条时,会报错。但是,当我们使用上述拼接SQL的方式进行批量插入时,数据量过大也会出现问题!我们可以先试验一下如何插入一批40000条数据。首先新建一张表作为插入数据的目标表:massive_data_insert_test。创建表“供应”。只是在一个SpringBoot项目中连接数据库并创建一个mapper,并写一条insert语句(创建项目、扫描mapper等操作这里不再赘述):下面是mapper接口和mapper.xml文件(里面有sql语句)。TestMapper.java@RepositorypublicinterfaceTestMapper.java{voidtestMassiveInsert(Listlist);}TestMapper.xmlinsertintomassive_data_insert_test(value1,value2)values(#{item.value1},#{item.value2})测试语句:@Service@Slf4jpublicclassTestService{//最大数量新批次privatestaticfinalintmaxInsertItemNumPerTime=500;私有TestMapper映射器;@AutowiredpublicTestService(TestMappermapper){this.mapper=mapper;}publicResulttestMassiveInsert(){longstartTime=System.currentTimeMillis();//获取开始时间Listlist=newLinkedList<>();//组装数据得到长度为500*80=40000的链表for(inti=0;ilist=newLinkedList<>();//将数据拼装得到长度为500*80=40000的链表for(inti=0;imaxInsertItemNumPerTime){List>all=newArrayList<>();诠释我=0;while(imapper.testMassiveInsert(o));}}catch(RuntimeExceptione){log.info("批量插入"+list.size()+"失败",e.getMessage());抛出新的RuntimeException(e);}longendTime=System.currentTimeMillis();//获取结束时间returnResult.ok().message("程序运行时间:"+(endTime-startTime)+"ms");}我们通过设置一个maxInsertItemNumPerTime来控制每批插入数据的长度3.简单测试下面是我的简单测试(即插入项总数为4w,但是当设置不同的maxInsertItemNumPerTime大小时,计算耗时执行比较程序)。不过这个测试没有多次取平均,网络也可能有抖动,所以只能算是一个简单的测试。20001000500250最后我在批量插入的时候选择了500作为每批的大小。上面我们说了,即使有数据库连接池提供的连接重用,如果和数据库的交互多了,还是会造成性能下降,所以这里的maxInsertItemNumPerTime并不是越小越好。同时,随着maxInsertItemNumPerTime变大,每次for循环中的SQL预处理过程(SQL拼接)时间会变长,而且这种增长不是线性的,而是呈指数增长(查了一些资料证实了我的猜测),否则,2000年不会远大于500。在实际业务中,还需要简单的测试来选择一个比较合适的值,总比没有测试好。四、做一些扩展其实Mybatis的官方文档提供了另一种方式来支持批量插入。不过由于公司的项目都是采用扫描Mapper的方式来操作数据库,而这种大数据插入的场景确实比较少,所以下面Mybatis提供的方式就不特意介绍了。多行插入是将多行插入表中的单个插入语句。这可能是一种向表中插入几行的便捷方法,但它有一些局限性:由于它是单个SQL语句,您可能会生成相当多的准备语句参数。例如,假设要向表中插入1000条记录,每条记录有5个字段。使用多行插入,您将生成一个包含5000个参数的SQL语句。JDBC准备语句中允许的参数数量有限制-这种插入很容易超过这些限制。如果你想插入很多记录,你可能应该改用JDBC批量插入(见下文)巨大的插入语句的性能可能低于你的预期。如果要插入很多记录,使用JDBC批量插入几乎总是更有效(见下文)。通过批量插入,JDBC驱动程序可以做一些优化这对于单个大型语句是不可能的使用多行插入检索生成的值可能是一个挑战。MyBatis目前有一些与在需要特殊考虑的多行插入中检索生成的键相关的限制(见下文)然而,多行插入有一些用例-特别是当你只想在表中插入几条记录并且不不需要检索生成的密钥。在那些情况下,多行插入将是一个简单的解决方案。翻译:try(SqlSessionsession=sqlSessionFactory.openSession()){List记录=getRecordsToInsert();//未显示MultiRowInsertStatementProvidermultiRowInsert=insertMultiple(records).into(generatedAlways).map(id).toProperty("id").map(firstName).toProperty("firstName").map(lastName).toProperty("lastName").build().render(RenderingStrategies.MYBATIS3);introws=mapper.insertMultiple(multiRowInsert);}以上是文档中的示例代码5.进一步优化代码由于我在公司实际开发中要做的是将数据导入到多个表中,如果我按照上面的方法批量写入,那么我的每一次插入都要有这样的逻辑://divide批量插入try{if(list.size()>maxInsertItemNumPerTime){List>all=newArrayList<>();诠释我=0;while(imapper.testMassiveInsert(o));}}catch(RuntimeExceptione){log.info("批量插入"+list.size()+"失败",e.getMessage());抛出新的RuntimeException(e);}显然,这是重复代码的一种难闻的味道好吧,它看起来很糟糕。那么我们来做一个简单的封装,把这段代码封装成一个方法,谁插入谁就调用谁。首先,这段代码需要传入的参数是什么?maxInsertItemNumPerTime不需要传入,因为很明显这是一个常量链表插入的内容链表,需要传入,而且类型也不全是HashMap,而是不同的表对应不同的实体类。通用类型T是必需的。mapper中的testMassiveInsert(HashMapmap)方法显然不是同一个方法在不同的表中插入mapper,所以这个也需要传入,如果传入一个方法作为参数,则需要Lambda表达式和函数表达式.编程。如果了解函数式接口,自然会想到这种只有输入没有输出的函数应该是Consumer传入的(反之对应Supplier,有输入有输出就是Function)。所以最终的抽象代码应该是这样的:publicvoidbatchSplitInsert(Listlist,ConsumerinsertFunc){List>all=newArrayList<>();如果(list.size()>maxInsertItemNumPerTime){inti=0;while(ilist.size()){subList=list.subList(i,list.size());}else{subList=list.subList(i,i+maxInsertItemNumPerTime);}i=i+maxInsertItemNumPerTime;all.add(subList);}all.parallelStream().forEach(insertFunc);}else{insertFunc.accept(列表);}}当我像这样插入不同的表时://LinkedlistofdatatobeinsertedListneedToInsert=...;//添加新的Consumer>consumer=o->mapper.insertTable(o);batchSplitInsert(需要插入,消费者);现在整个世界都是优雅的!如果你是新手,或者刚学Java,对函数式编程了解不多,最好从简单的Lambda语句开始。这真是Java8超级好用的一个特性。至此,本篇就算结束了,如果大家有什么问题,欢迎大家留言,让我们一起交流。如果本文对您有帮助,请多多点赞,谢谢!如果有人有更好的实现方法,请告诉我!