Mybatis 批量插入数据 SQL
温故而知新,这里记录一下
案例1
批量插入时,xxxMapper.java 中方法的参数都必须是 List ,泛型可以是 bean ,也可以是 Map 。配合使用 mybatis 的 foreach 即可。示例如下:
public Integer batchInsertDemo(List<Demo> list);
首先对于批量数据的插入有两种解决方案
1、for 循环调用 Dao 中的单条插入方法
2、传一个 List<Object> 参数,使用 MyBatis 的 foreach 标签进行批量插入
<insert id="addUser" parameterType="java.util.List" >insert into user(name,age) values<foreach collection="list" item="item" index="index" separator=",">(#{item.name},#{item.age})</foreach>
</insert>
性能上对比,批量插入性能好,更加省时间。
原因如下:
- 循环插入:需要每次都获取 session, 获取连接,然后将 SQL 语句发给 MySQL 去执行(JDBC 一般情况下是通过 TCP/IP 进行连接和数据库进行通信的)。可以看这里 mysql 四种通信协议
- 批量插入: 批量插入通过 foreach 标签,将多条数据拼接在 SQL 语句后,一次执行只获取一次 session, 提交一条 SQL 语句。减少了程序和数据库交互的准备时间。
1、只批量插入数值
这种写法适合插入数据的项不变,即 sql 中 VALUES 前括号中的列不变。若插入的项有所变化则适用下一种方法。
<insert id="batchInsertDemo" parameterType="java.util.List" >INSERT INTO demo(id,name,code,age,address) VALUES <foreach collection="list" item="item" index="index" separator="," > (#{item.id},#{item.name},#{item.code},#{item.age},#{item.address}) </foreach>
</insert>
2、根据数值变动插入选项
此时需适用 foreach 循环包含整个sql语句,VALUES 前后括号中的插入项和插入数据使用 trim 标签,再配合使用 if 标签即可。示例如下:
<insert id="batchInsertDemo" parameterType="list" ><foreach collection="list" item="item" index="index" separator=";">INSERT INTO demo<trim prefix="(" suffix=")" suffixOverrides="," ><if test="item.id!= null">id,</if><if test="item.name!= null">name,</if><if test="item.code != null">code,</if><if test="item.age!= null">age,</if><if test="item.address!= null">address,</if></trim><trim prefix="values (" suffix=")" suffixOverrides="," >if test="item.id!= null">#{item.id,jdbcType=INTEGER},</if><if test="item.name!= null">#{item.name,jdbcType=VARCHAR},</if><if test="item.code != null">#{item.code ,jdbcType=VARCHAR},</if><if test="item.age!= null">#{item.age,jdbcType=INTEGER},</if><if test="item.address!= null">#{item.address,jdbcType=VARCHAR},</if></trim></foreach></insert>
本案2
例的建表语句是:
-- auto-generated definition
create table contact_type
(
sid varchar(50) not null
primary key,
name varchar(50) default '' null,
status int default 1 null comment '状态,默认1表示有效,0为冻结',
seq float default 0 null,
create_time datetime default CURRENT_TIMESTAMP null
)
comment '往来单位类型';
所以主键是字符串类型,而不是自增类型。写在 Mybatis 的 xml 文件中的SQL语句如下:
<insert id="saveOne" parameterType="com.ccsoft.femis.model.ContactType"><!--<selectKey resultType="java.lang.Integer" keyProperty="id" order="AFTER">--><!--SELECT LAST_INSERT_ID()--><!--</selectKey>-->insert into contact_type<trim prefix="(" suffix=")" suffixOverrides=","><if test="create_time != null"> create_time, </if><if test="name != null"> name, </if><if test="seq != null"> seq, </if><if test="sid != null"> sid, </if><if test="status != null"> status, </if></trim>values<trim prefix="(" suffix=")" suffixOverrides=","><if test="create_time != null"> #{create_time},</if><if test="name != null"> #{name},</if><if test="seq != null"> #{seq},</if><if test="sid != null"> #{sid},</if><if test="status != null"> #{status},</if></trim>ON DUPLICATE KEY UPDATE<trim suffixOverrides=","><if test="create_time != null"> create_time = #{create_time}, </if><if test="name != null"> name = #{name}, </if><if test="seq != null"> seq = #{seq}, </if><if test="sid != null"> sid = #{sid}, </if><if test="status != null"> status = #{status}, </if></trim></insert>
可以看到由于使用了 <if test=...> ,如果 Java 端传递来的对象有部分属性没有设置,导致对象中该属性是空那么最终执行的 SQL 语句中就不会有该字段。
测试表创建的主键字段非自增,所以将上面 xml 中的头部 SQL 语句(返回新增行的主键字段值)给注释掉了,如果你的表示有自增字段的去掉注释,即可得到新增的行的自增字段数值。
通过在 java 中测试,发现上面的语句新增一行成功后会返回1,修改成功后会返回2(这里有疑惑,如果清楚原因的麻烦跟帖科普下)。
如果有未设置的属性恰好在数据库端对应的字段被设置为非空并且没有默认值导致新增或者修改失败那么会报异常 java.sql.SQLException 。
批量写入数据有则修改无则新增,同时判断空选择性写入字段
数据表还是上面的,直接贴出写在 Mybatis 的 XML 文件中的 SQL 是:
<insert id="saveBatch" parameterType="java.util.List"><!--<selectKey resultType="java.lang.String" keyProperty="sid" order="AFTER">--><!--SELECT LAST_INSERT_ID()--><!--</selectKey>--><foreach collection ="list" item="ele" index= "index" separator =";">insert into contact_type<trim prefix="(" suffix=")" suffixOverrides=","><if test="ele.create_time != null"> create_time, </if><if test="ele.name != null"> name, </if><if test="ele.seq != null"> seq, </if><if test="ele.sid != null"> sid, </if><if test="ele.status != null"> status, </if></trim>values<trim prefix="(" suffix=")" suffixOverrides=","><if test="ele.create_time != null"> #{ele.create_time},</if><if test="ele.name != null"> #{ele.name},</if><if test="ele.seq != null"> #{ele.seq},</if><if test="ele.sid != null"> #{ele.sid},</if><if test="ele.status != null"> #{ele.status},</if></trim>ON DUPLICATE KEY UPDATE<trim suffixOverrides=","><if test="ele.create_time != null"> create_time = #{ele.create_time}, </if><if test="ele.name != null"> name = #{ele.name}, </if><if test="ele.seq != null"> seq = #{ele.seq}, </if><if test="ele.sid != null"> sid = #{ele.sid}, </if><if test="ele.status != null"> status = #{ele.status}, </if></trim></foreach></insert>
上面代码中在 SQL 语句的最外层使用了 for 循环,好处是将 List<ContactType> 类型的集合传递来写入数据时可以有的是新增有的是修改,例如3行数据,第一三行由于主键字段对应属性 sid 被设置为 NULL ,会向数据库中新增行,第二行数据设置了 sid ,并且该值在数据库中有对应行,那么会修改数据库中的该行上的数据。不过这种做法也有问题,就是返回给 Java 的数据永远都是1,因为每个对象构成的 SQL 语句间使用的间隔符号是 ; ,那么最终返回的影响的行数是最后一条 SQL 语句影响的行数。对此有其他见解的话麻烦跟帖科普下。
例子3
<insert id="insertSelective" parameterType="com.yimayhd.snscenter.client.domain.ComentDO" useGeneratedKeys="true" keyProperty="id" >insert into com_coment<trim prefix="(" suffix=")" suffixOverrides="," >domain,status,<if test="gmtCreated != null" >gmt_created,</if><if test="gmtModified != null" >gmt_modified,</if></trim><trim prefix="values (" suffix=")" suffixOverrides="," >#{domain,jdbcType=INTEGER},#{status,jdbcType=INTEGER},<if test="gmtCreated != null" >#{gmtCreated,jdbcType=TIMESTAMP},</if><if test="gmtModified != null" >#{gmtModified,jdbcType=TIMESTAMP},</if></trim></insert>
注意事项
注意1
MySQL默认接受sql的大小是1048576(1M),即第三种方式若数据量超过1M会报如下异常:(可通过调整MySQL安装目录下的my.ini文件中[mysqld]段的"max_allowed_packet = 1M")
nested exception is com.mysql.jdbc.PacketTooBigException: Packet for query is too large (5677854 > 1048576).
You can change this value on the server by setting the max_allowed_packet' variable.
注意2
由于上面的 SQL 在数据库端是多条语句,需要在 Java 连接数据库的字串中设置 &allowMultiQueries=true
返回值
对于普通的单条插入,数据库的返回值就是 (0/1) 。
对于返回值代表的意思可以认为是“语句执行返回的数据库受影响的行数。”或者是“此次执行是否成功(0 - 失败,1 - 成功)。”
对应的也就是在 Dao 层中,对于插入方法的返回值类型的设定有(int/boolean)两种。
对于批量插入的返回值,返回的还是(0/1), 而不是统计插入成功几条,即使你的 Dao 层方法的返回值类型为 int.
这里的(0/1) 也就代表着,这次批量插入是否成功(0 - 失败,1 - 成功)。
当然你 Dao 层的返回值还是可以是(int/boolean)
批量插入中间有一个失败会怎么样
猜想有下面三种情况
- a、继续插入后面的,把失败的跳过
- b、停止插入,但前面的插入成功保持。
- c、全部回滚
这里就直接放结果了。
批量语句,只要有一个失败,就会全部失败。数据库会回滚全部数据。(原子性)
关于测试过程可以看这篇博客:mysql 批量插入语句执行失败的话,是部分失败还是全部失败
其实也很好理解。
首先我们知道了 MyBatis <foreach> 批量插入,是在程序内拼接 SQL 语句(拼接成多条同时插入的 SQL 语句),拼接后发给数据库。
就相当于咱们自己在 MySQL 的命令行中,执行一条多插入的语句。默认情况下 MySQL 单条语句是一个事务,这在一个事务范围内,当中间的 SQL 语句有问题,或者有一个插入失败,就会触发事务回滚。同时你也能看到错误提示。(命令行执行单条 SQL 的情况)
所以有一个插入不成功肯定全部回滚。
批量插入数据量的限制
我这里就直接放结论,又兴趣的可以看这篇博客有探究过程 : Mybatis 批量插入引发的血案
1、MyBatis 本身对插入的数据量没有限制
2、MySQL 对语句的长度有限制,默认是 4M
其他数据库的情况这里不介绍,可以自行百度。通过上面 “MySQL 对语句的长度有限制,默认是 4M” 我们可以知道,批量插入数据是有限制的。不能一下把几万条数据(就是太大数据量意思)一次性插入。
所以一般情况下我们推荐即使使用批量插入,也要分批次。
每次批次设置多少?需要根据你的插入一条数据的参数量来做度量。因为受限条件是 SQL 语句的长度。
而且分批插入更加合理,对于插入失败,回滚范围会缩小很多。
对空集合参数进行校验
MyBatis 并没有做集合容量的验证,如果集合参数为空或者 size 为 0 则生成的 SQL 可能只有”insert into user (name,age) values” 这样一段或者没有,所以说,写批量 SQL 的时候注意在调用批量方法的地方加入对容量的验证。
另外一种 foreach 插入(不推荐)
<insert id="addBatchUser" parameterType="java.util.List" ><foreach collection="list" item="item" index="index" separator=";">insert into user(name,age) values(#{item.name},#{item.age})</foreach>
</insert>
这种写法也能实现批量插入。但是有很多问题。
- a、首先这种方式的批量插入也是 SQL 拼接。但是明显字符长度增加。这就导致每批次可插入的数量减少
- b、这种方式执行返回值还是(0、1)是已经尝试插入的最后一条数据是否成功。由于这种 foreach 拼接成的 SQL 语句,是以分号 “;” 分隔的多条 insert 语句。这就导致前面的数据项都插入成功了。(默认数据库的事务处理是单条提交的,出错前的执行都是一个个单条语句,所以并并没有回滚数据。)
所以如果你想中间插入失败回滚的话,需要使用 Spring 事务,但是还需要注意 spring 事务是抛出运行时异常时才会回滚。这种批量插入中间有没插入成功的是不会抛出异常的。所以你需要根据返回值判断手动编码抛出异常。
而最上面的那种写法就不用是用事务,因为他是一条 SQL 语句。
<foreach> 标签
foreach 的主要用在构建 in 条件中,它可以在 SQL 语句中进行迭代一个集合。
foreach 元素的属性主要有 item,index,collection,open,separator,close。 item 表示集合中每一个元素进行迭代时的别名 index 指定一个名字,用于表示在迭代过程中,每次迭代到的位置 open 表示该语句以什么开始 separator 表示在每次进行迭代之间以什么符号作为分隔符 close 表示以什么结束
在使用 foreach 的时候最关键的也是最容易出错的就是 collection 属性,该属性是必须指定的,但是在不同情况 下,该属性的值是不一样的,主要有一下 3 种情况:
- 1、如果传入的是单参数且参数类型是一个 List 的时候,collection 属性值为 list
- 2、如果传入的是单参数且参数类型是一个 array 数组的时候,collection 的属性值为 array
- 3、如果传入的参数是多个的时候,我们就需要把它们封装成一个 Map 了,当然单参数也可以封装成 map
参考
Mybatis 批量插入数据 SQL-腾讯云开发者社区-腾讯云
Mybatis 批量写入有则修改无则新增,同时判断空选择性写入字段_mybatis批量新增并且判断空-CSDN博客
https://www.cnblogs.com/fantastic-clouds/p/13090557.html
MyBatis 批量插入 - Yaxing's Blog
相关文章:
Mybatis 批量插入数据 SQL
温故而知新,这里记录一下 案例1 批量插入时,xxxMapper.java 中方法的参数都必须是 List ,泛型可以是 bean ,也可以是 Map 。配合使用 mybatis 的 foreach 即可。示例如下: public Integer batchInsertDemo(List<D…...

【学员分享-考试心得】国产数据库潜力无限,云贝教育OBCP认证培训帮您解难题
近年来,随着国产化转型的推进,国外数据库的岗位需求逐渐减少,让许多IT从业者倍感压力。在这种情况下,了解国产数据库成为了求职市场上的竞争力。云贝老师们将聚焦于OceanBase、PostgreSQL、TDSQL等IT培训,探讨其对国产…...

【Mysql】事务的隔离级别与 MVCC
事务隔离级别 我们知道 MySQL 是一个 C/S 架构的服务,对于同一个服务器来说,可以有多个客户端与之连接,每个客户端与服务器连接上之后,就是一个会话( Session )。每个客户端都可以在自己的会话中向服务器发…...

MongoDB从入门到实战之MongoDB快速入门
前言 上一章节主要概述了MongoDB的优劣势、应用场景和发展史。这一章节将快速的概述一下MongoDB的基本概念,带领大家快速入门MongoDB这个文档型的NoSQL数据库。 MongoDB从入门到实战的相关教程 MongoDB从入门到实战之MongoDB简介👉 MongoDB从入门到实战…...

Linux服务详解
如有错误或有补充,以及任何改进的意见,请在评论区留下您的高见,同时文中给出大部分命令的示例,即是您暂时无法在Linux中查看,您也可以知道各种操作的功能以及输出 如果觉得本文写的不错,不妨点个赞&#x…...

闲聊电脑(4)硬盘分区
夜深人静,万籁俱寂,老郭趴在电脑桌上打盹,桌子上的小黄鸭和桌子旁的冰箱又开始窃窃私语…… 小黄鸭:冰箱大哥,上次你说的那个“分区”和“格式化”是什么意思? 冰箱:分区么,就是分…...

光耦合器的结构与原理解析
光耦合器是一种重要的电光转换器件,广泛应用于电子设备、通信系统以及工业控制等领域。本文将深入分析光耦合器的结构与原理,旨在为读者提供清晰而全面的了解。 光耦合器作为一种关键的电子元件,扮演着信号隔离和传输的重要角色。它的设计结构…...

代码随想录day17--二叉树的应用5
LeetCode654.最大二叉树 题目描述: 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点,其值为 nums 中的最大值。递归地在最大值 左边 的 子数组前缀上 构建左子树。递归地在最大值 右边 的 子数组后…...

跟着cherno手搓游戏引擎【19】抽象纹理
引入: 导入stb_image: GitHub - nothings/stb: stb single-file public domain libraries for C/C 下载复制stb_image.h的内容(8000多行),然后粘到如图位置 stb_image.cpp: #include"ytpch.h" #define STB_IMAGE_IM…...

CentOS 7中搭建NFS文件共享服务器的完整步骤
CentOS 7中搭建NFS文件共享服务器的完整步骤 要求:实现镜像文件共享,并基于挂载的共享目录配置yum源。 系统环境: 服务器:172.20.26.167-CentOS7.6 客户端:172.20.26.198-CentOS7.6 1、在服务器和客户端上&#x…...
【华为OD机试】 最小矩阵宽度【2024 C卷|100分】
【华为OD机试】-真题 !!点这里!! 【华为OD机试】真题考点分类 !!点这里 !! 题目描述 给定一个矩阵,包含 N * M 个整数,和一个包含 K 个整数的数组。 现在要求在这个矩阵中找一个宽度最小的子矩阵,要求子矩阵包含数组中所有的整数。 输入描述 第一行输入两个正整数 N,M,…...

Ingress
文章目录 环境准备什么是 Ingress认识 Ingress 资源Ingress 控制器(controller)Ingress 规则pathType 路径类型多重匹配Ingress 类TLS生成证书创建密钥 环境准备 下面的 yaml 文件内容,是使用 sts 创建两个 web 服务,并配置对应的 servcie。web 服务的首…...
MySQL数据库安全加固方案
数据库版本:MySQL8.0.22 按照本安全加固方案进行的数据库加固,一般安全扫描工具扫描出来几乎无漏洞。 1.2 帐号安全 1.2.1 避免不同用户间共享帐号 参考以下步骤。 A. 创建用户。 mysql>CREATE USER ‘用户名’@‘用户 host’ IDENTIFIED BY ‘密码’; 执行以上命令可以…...
实践:读取html文本提取相应内容按照格式导出到excel中
最近在做一个需求,需要将html文本中的内容提取出来,然后导出到excel里面,实现交代情景,html文本中存在许多标签,且很乱,因此需要之间将标签里面的文本提取出来,再进行处理。 ............String…...
oracle 修改表结构语句
oracle 修改表结构语句 Oracle中可以使用ALTER TABLE语句来修改表的结构。 添加列: ALTER TABLE table_name ADD column_name data_type; 示例:ALTER TABLE employees ADD email VARCHAR2(50); 删除列: ALTER TABLE table_name DROP COL…...

LabVIEW核能设施监测
LabVIEW核能设施监测 在核能领域,确保设施运行的安全性和效率至关重要。LabVIEW通过与硬件的紧密集成,为高温气冷堆燃料装卸计数系统以及脉冲堆辐射剂量监测与数据管理系统提供了解决方案。这些系统不仅提高了监测和管理的精确度,也保证了核…...
使用Process.Start()打开文件夹时出现访问被拒绝异常
默认的打开形式 Process.Start(folderPath); 解决方案 System.Diagnostics.Process.Start(Environment.GetEnvironmentVariable("WINDIR") "\explorer.exe", folderPath); 参考文献 c# - 使用 Process.Start() 打开文件夹时访问被拒绝异常 - IT工具网…...

spdk技术原理简介和实践经验
一、导读 与机械硬盘相比,NVMe-ssd在性能、功耗和密度上都有巨大的优势,并且随着固态存储介质的高速发展,其价格也在大幅下降,这些优势使得NVMe-ssd在分布式存储中使用越来越广泛。由于NVMe-ssd的性能比传统磁盘介质高出很多&…...

【开源】基于JAVA+Vue+SpringBoot的用户画像活动推荐系统
目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 兴趣标签模块2.3 活动档案模块2.4 活动报名模块2.5 活动留言模块 三、系统设计3.1 用例设计3.2 业务流程设计3.3 数据流程设计3.4 E-R图设计 四、系统展示五、核心代码5.1 查询兴趣标签5.2 查询活动推荐…...

023 for循环详解
什么是for循环 // 练习1 int odd 0; int even 0; for (int i 0; i < 100; i) {if (i % 2 0) {even i;} else {odd i;} } System.out.println("奇数和为:" odd ",偶数和为:" even);// 练习2 for (int i 1; i < 1000; i) {if (i % 5 0) {Sy…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...

12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...

android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...

WPF八大法则:告别模态窗口卡顿
⚙️ 核心问题:阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程,导致后续逻辑无法执行: var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题:…...

系统掌握PyTorch:图解张量、Autograd、DataLoader、nn.Module与实战模型
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文通过代码驱动的方式,系统讲解PyTorch核心概念和实战技巧,涵盖张量操作、自动微分、数据加载、模型构建和训练全流程&#…...