MyBatis原生批量插入的坑与解决方案!

353e1bc59b9f3be58c495390b922fc5c.png

作者 | 王磊

来源 | Java中文社群(ID:javacn666)

转载请联系授权(微信ID:GG_Stone)

前面的文章咱们讲了 MyBatis 批量插入的 3 种方法:循环单次插入、MyBatis Plus 批量插入、MyBatis 原生批量插入,详情请点击《MyBatis 批量插入数据的 3 种方法!》

但之前的文章也有不完美之处,原因在于:使用 「循环单次插入」的性能太低,使用「MyBatis Plus 批量插入」性能还行,但要额外的引入 MyBatis Plus 框架,使用「MyBatis 原生批量插入」性能最好,但在插入大量数据时会导致程序报错,那么,今天咱们就会提供一个更优的解决方案。

原生批量插入的“坑”

首先,我们来看一下 MyBatis 原生批量插入中的坑,当我们批量插入 10 万条数据时,实现代码如下:

import com.example.demo.model.User;
import com.example.demo.service.impl.UserServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.ArrayList;
import java.util.List;@SpringBootTest
class UserControllerTest {// 最大循环次数private static final int MAXCOUNT = 100000;@Autowiredprivate UserServiceImpl userService;/*** 原生自己拼接 SQL,批量插入*/@Testvoid saveBatchByNative() {long stime = System.currentTimeMillis(); // 统计开始时间List<User> list = new ArrayList<>();for (int i = 0; i < MAXCOUNT; i++) {User user = new User();user.setName("test:" + i);user.setPassword("123456");list.add(user);}// 批量插入userService.saveBatchByNative(list);long etime = System.currentTimeMillis(); // 统计结束时间System.out.println("执行时间:" + (etime - stime));}
}

核心文件 UserMapper.xml 中的实现代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper"><insert id="saveBatchByNative">INSERT INTO `USER`(`NAME`,`PASSWORD`) VALUES<foreach collection="list" separator="," item="item">(#{item.name},#{item.password})</foreach></insert></mapper>

当我们开心地运行以上程序时,就出现了以下的一幕:

ae6bc4b918b7ce56749da31e73fe78b3.png沃,程序竟然报错了!

这是因为使用 MyBatis 原生批量插入拼接的插入 SQL 大小是 4.56M,而默认情况下 MySQL 可以执行的最大 SQL 为 4M,那么在程序执行时就会报错了。

解决方案

以上的问题就是因为批量插入时拼接的 SQL 文件太大了,所以导致 MySQL 的执行报错了。那么我们第一时间想到的解决方案就是将大文件分成 N 个小文件,这样就不会因为 SQL 太大而导致执行报错了。也就是说,我们可以将待插入的 List 集合分隔为多个小 List 来执行批量插入的操作,而这个操作过程就叫做 List 分片。

有了处理思路之后,接下来就是实操了,那如何对集合进行分片操作呢?

分片操作的实现方式有很多种,这个我们后文再讲,接下来我们使用最简单的方式,也就是 Google 提供的 Guava 框架来实现分片的功能。

分片 Demo 实战

要实现分片功能,第一步我们先要添加 Guava 框架的支持,在 pom.xml 中添加以下引用:

<!-- google guava 工具类 -->
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>31.0.1-jre</version>
</dependency>

接下来我们写一个小小的 demo,将以下 7 个人名分为 3 组(每组最多 3 个),实现代码如下:

import com.google.common.collect.Lists;import java.util.Arrays;
import java.util.List;/*** Guava 分片*/
public class PartitionByGuavaExample {// 原集合private static final List<String> OLD_LIST = Arrays.asList("唐僧,悟空,八戒,沙僧,曹操,刘备,孙权".split(","));public static void main(String[] args) {// 集合分片List<List<String>> newList = Lists.partition(OLD_LIST, 3);// 打印分片集合newList.forEach(i -> {System.out.println("集合长度:" + i.size());});}
}

以上程序的执行结果如下:

f949feef2370c127a2f63c7923b69ed0.png从上述结果可以看出,我们只需要使用 Guava 提供的 Lists.partition 方法就可以很轻松的将一个集合进行分片了。

原生批量插入分片实现

那接下来,就是改造我们的 MyBatis 批量插入代码了,具体实现如下:

@Test
void saveBatchByNativePartition() {long stime = System.currentTimeMillis(); // 统计开始时间List<User> list = new ArrayList<>();// 构建插入数据for (int i = 0; i < MAXCOUNT; i++) {User user = new User();user.setName("test:" + i);user.setPassword("123456");list.add(user);}// 分片批量插入int count = (int) Math.ceil(MAXCOUNT / 1000.0); // 分为 n 份,每份 1000 条List<List<User>> listPartition = Lists.partition(list, count);// 分片批量插入for (List<User> item : listPartition) {userService.saveBatchByNative(item);}long etime = System.currentTimeMillis(); // 统计结束时间System.out.println("执行时间:" + (etime - stime));
}

执行以上程序,最终的执行结果如下:

14d99085c1d33b59d6dc2307a8662ef7.png

从上图可以看出,之前批量插入时的异常报错不见了,并且此实现方式的执行效率竟比 MyBatis Plus 的批量插入的执行效率要高,MyBatis Plus 批量插入 10W 条数据的执行时间如下:

fc3ebfcf800b382818a0faea344b1a8f.png

总结

本文我们演示了 MyBatis 原生批量插入时的问题:可能会因为插入的数据太多从而导致运行失败,我们可以通过分片的方式来解决此问题,分片批量插入的实现步骤如下:

  1. 计算出分片的数量(分为 N 批);

  2. 使用 Lists.partition 方法将集合进行分片(分为 N 个集合);

  3. 循环将分片的集合进行批量插入的操作。

e0db0453aeb5320573417a407391edf9.gif

往期推荐

310ebdbc4c9f26244e2ede250b2f3f3e.png

MyBatis 批量插入数据的 3 种方法!


9c87eafdca7bbba14684a6152d242efa.png

MyBatis Plus 批量数据插入功能,yyds!


f580758173002fec247ee27bfd1424aa.png

如何给SpringBoot配置轻松加密?


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/544536.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring学习(20)--- Schema-based AOP(基于配置的AOP实现) -- 配置切入点pointcut

pointcut&#xff08;切断点&#xff09;表达式&#xff1a; execution(public * *(..)) execution(* set*(..)) execution(* com.xyz.service.AccountService.*(..)) execution(* com.xyz.service..(..)) execution(* com.xyz.service...(..)) within(com.xyz.service.*)…

系统结构图 数据结构_数据结构图简介

系统结构图 数据结构What you are going to learn? 你要学什么&#xff1f; In this article, we learn about the introduction to Graphs in Data Structure and Algorithm. 在本文中&#xff0c;我们将了解图在数据结构和算法中的介绍 。 What are the components in Gra…

Matlab仿真PID控制(带M文件、simulink截图和参数分析)

文章目录0.符号说明1.如何根据连续系统建立差分方程1.1.获取连续系统的传递函数1.2.获取离散系统的传递函数1.3.转换为差分方程2.基本PID控制原理3.比较PID输出&#xff0c;分析参数产生的影响4.改进PID算法&#xff08;遇限削弱积分法&#xff09;5.simulink仿真0.符号说明 y…

再见 Postman!Apifox 才是 YYDS!

作为开软件开发从业者&#xff0c;API 调试是必不可少的一项技能&#xff0c;在这方面 Postman 做的非常出色。但是在整个软件开发过程中&#xff0c;API 调试只是其中的一部分&#xff0c;还有很多事情 Postman 无法完成&#xff0c;或者无法高效完成&#xff0c;比如&#xf…

Python operator.not_()函数与示例

operator.not_()函数 (operator.not_() Function) operator.not_() function is a library function of operator module, it is used to perform "NOT operation" on a given value and returns True if the given value is zero or any falsy value, False, otherw…

jprofiler安装与使用

1&#xff1a; 修改/etc/profile 增加以下内容&#xff1a; JPROFILER_HOME/opt/jprofiler9/bin/linux-x64export LD_LIBRARY_PATH$LD_LIBRARY_PATH:$JPROFILER_HOME 2&#xff1a;tomcat /bin/catalina.sh 增加以下内容&#xff1a; CATALINA_OPTS"$CATALINA_OPTS -Xms12…

Matlab【可视化作图】绘制线电压相电压辅助线

目录引言绘图原理采点绘图设置坐标轴标尺引言 学习电力电子的同学可能在私下里练习的时候非常需要三相线电压和相电压的辅助线。最近我随便找了一本书把Matlab可视化编程恶补了一下&#xff0c;给大家介绍一下这个波形辅助线是怎么做的。 三相线电压辅助线就是一组相位相差60的…

SpringBoot实现Excel导入导出,好用到爆,POI可以扔掉了!

在我们平时工作中经常会遇到要操作Excel的功能&#xff0c;比如导出个用户信息或者订单信息的Excel报表。你肯定听说过POI这个东西&#xff0c;可以实现。但是POI实现的API确实很麻烦&#xff0c;它需要写那种逐行解析的代码&#xff08;类似Xml解析&#xff09;。今天给大家推…

Python datetime __str __()方法与示例

Python datetime .__ str __()方法 (Python datetime.__str__() Method) datetime.__str__() method is used to manipulate objects of datetime class of module datetime. datetime .__ str __()方法用于操作模块datetime的datetime类的对象。 It uses a datetime class o…

Facebook升级到MySQL 8.0付出的代价

近日&#xff0c;Facebook 官博公布了他们的数据库版本从 MySQL 5.6 升级到了 MySQL 8.0&#xff0c;并且在官博记录了复盘详细的升级过程。Facebook 称&#xff0c;他们最近的一次大版本升级到 MySQL 5.6 花了一年多时间才完成&#xff0c;还在 5.6 版上开发 LSM 树存储引擎&a…

Matlab制作朱利表

朱利判据 其中 {bn−kan−k−ana0∗akcn−kbn−k−bnb0∗bk...qn−kpn−k−pnp0∗pk\begin{cases} b_{n-k}a_{n-k}-\frac{a_n}{a_0}*a_k\\ c_{n-k}b_{n-k}-\frac{b_n}{b_0}*b_k\\ ...\\ q_{n-k}p_{n-k}-\frac{p_n}{p_0}*p_k \end{cases}⎩⎪⎪⎪⎨⎪⎪⎪⎧​bn−k​an−k​−a0…

HDOJ 1047 Integer Inquiry

JAVA睑板.... Integer Inquiry Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 12042 Accepted Submission(s): 3037Problem DescriptionOne of the first users of BITs new supercomputer was Chip Diller. …

高并发下秒杀商品,必须知道的9个细节

高并发下如何设计秒杀系统&#xff1f;这是一个高频面试题。这个问题看似简单&#xff0c;但是里面的水很深&#xff0c;它考查的是高并发场景下&#xff0c;从前端到后端多方面的知识。秒杀一般出现在商城的促销活动中&#xff0c;指定了一定数量&#xff08;比如&#xff1a;…

weakhashmap_Java WeakHashMap keySet()方法与示例

weakhashmapWeakHashMap类的keySet()方法 (WeakHashMap Class keySet() method) keySet() method is available in java.util package. keySet()方法在java.util包中可用。 keySet() method is used to retrieve all the key exists in this map to be viewed in a set. keySet…

最小拍控制系统详细解读(阶跃输入+速度输入2个案例)【Simulink仿真】

目录索引1.符号说明与结构框图2.最小拍控制系统构造原则2.1数字控制器D(z)的构造3.简单控制对象的最小拍控制器设计3.1阶跃输入3.2速度输入1.符号说明与结构框图 y(k)——系统响应输出的离散值u(k)——数字PID控制输出的离散值r(k)——期望输出的离散值&#xff08;事先已知&a…

php中重写和final关键字的使用

为什么把重写和final放在一起&#xff0c;原因就是一条&#xff1a;final的意思是不可更改的&#xff0c;也就是说final定义的东西是不可改变的&#xff0c;下面具体来说一下。 来看一段简单的代码&#xff1a; 1 class BaseClass {2 final public function test() {3 …

java vector_Java Vector lastElement()方法与示例

java vector向量类的lastElement()方法 (Vector Class lastElement() method) lastElement() method is available in java.util package. lastElement()方法在java.util包中可用。 lastElement() method is used to return the last element of this Vector. lastElement()方法…

SpringBoot官方热部署和远程调试神器,真带劲!

平时使用SpringBoot开发应用时&#xff0c;修改代码后需要重新启动才能生效。如果你的应用足够大的话&#xff0c;启动可能需要好几分钟。有没有什么办法可以加速启动过程&#xff0c;让我们开发应用代码更高效呢&#xff1f;今天给大家推荐一款SpringBoot官方的热部署工具spri…

【Python】输入任意个数元素并保存至列表

目录1.导入任意个数元素到列表1.1.编程思路1.2.代码片2.查找一个重复元素在列表中的所有位置2.1.编程思路2.2代码片1.导入任意个数元素到列表 1.1.编程思路 输入未知个数的元素需要用列表来存储&#xff0c;由于Python具有内存的动态分配能力&#xff0c;列表不需要手动动态分…

MySQL 性能优化的 9 种姿势,面试再也不怕了!

大家好&#xff0c;我是磊哥&#xff01;今天给大家分享一些简单好用的数据库优化方式&#xff01;1、选择最合适的字段属性Mysql是一种关系型数据库&#xff0c;可以很好地支持大数据量的存储&#xff0c;但是一般来说&#xff0c;数据库中的表越小&#xff0c;在它上面执行的…