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

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

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

相关文章

vue 项目涉及的焦点聚焦、格式化日期、判断是否为对象或数组、判断是否为空、深拷贝、节流、防抖

焦点聚焦 import Vue from vue // 插件对象(必须有 install 方法, 才可以注入到 Vue.use 中) export default {install () {Vue.directive(fofo, {inserted (el) {el el.querySelector(input)el.focus()}})} }格式化日期格式 export const formatDate (time) > {// 将xx…

GPT-4成为职场得力助手

在这个快节奏的时代&#xff0c;工作效率和创新能力成为了职场竞争的关键。幸运的是&#xff0c;随着人工智能的发展&#xff0c;我们现在有了一个强大的新伙伴——GPT-4。这不仅是一个简单的工具&#xff0c;而是一个能够与我们对话、帮助我们解决问题的智能助手。那么&#x…

C++多线程3

生产者消费者模型 OS经典问题&#xff0c;生产者消费者模型,empty和full还有mutex对应到C上如何处理看代码即可 #include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <queue> using namespace st…

数据中心机房建设的关键痛点及解决方案

随着信息技术的飞速发展&#xff0c;数据中心机房已成为企业信息系统的核心。然而&#xff0c;在机房系统的建设过程中&#xff0c;投资及运行维护成为项目管理的关键痛点。合理的投资决策和高效的运维管理是确保机房系统经济性和可靠性的重要因素。本文将探讨机房系统建设的投…

那些不输于乙游男主人设的国漫男主

最近乙游的势头越来越猛&#xff0c;新宠旧爱一起上阵&#xff0c;叫人应接不暇。在二次元的世界里&#xff0c;乙游男主们凭借着超凡的魅力&#xff0c;成为了无数少女心中的理想对象。他们或冷酷、或温柔、或阳光、或神秘&#xff0c;每一个角色都有着独特的性格和故事。 乙游…

NLP_统计语言模型的发展历程

文章目录 统计语言模型发展的里程碑&#xff1a; 上半部分是语言模型技术的进展&#xff1b;下半部分则是词向量&#xff08;词的表示学习&#xff09;技术的发展。其中&#xff0c;词向量表示的学习为语言模型提供了更高质量的输入信息&#xff08;词向量表示&#xff09; 1…

性能测试常用术语

之前在性能测试过程中&#xff0c;对于某些其中的术语一知半解&#xff0c;导致踩了很多坑。这篇博客&#xff0c;就常见的一些性能测试术语进行一次浅析。。。 负载 对被测系统不断施加压力&#xff0c;直到性能指标超过预期或某项资源使用达到饱和&#xff0c;以验证系统的处…

玩美移动为花西子海外官网打造AR虚拟试妆决方案

全球领先的增强现实&#xff08;AR&#xff09;及人工智能&#xff08;AI&#xff09;美妆科技领导者及玩美系列APP开发商——玩美移动&#xff08;纽交所代码&#xff1a;PERF&#xff09;于近日宣布携手知名美妆品牌花西子&#xff0c;在其线海外官方网页提供多项彩妆虚拟试妆…

C语言搭配EasyX实现贪吃蛇小游戏

封面展示 内部展示 完整代码 #define _CRT_SECURE_NO_WARNINGS #include<easyx.h> #include<stdio.h> #include<mmsystem.h> #pragma comment (lib,"winmm.lib") #define width 40//宽有40个格子 #define height 30//长有40个格子 #define size 2…

ubuntu22.04安装部署02:禁用显卡更新

一、查看可用显卡驱动 ubuntu-drivers devices 二、查看显卡信息 # -i表示不区分大小写 lspci | grep -i nvidia nvidia-smi 三、查看已安装显卡驱动 cat /proc/driver/nvidia/version 四、锁定显卡升级 使用cuda自带额显卡驱动&#xff0c;居然无法&#xff0c;找到如何锁…

TRUNCATE TABLE和DELETE FROM对比

相同点:用于删除数据,同时保留表结构. 不同点: TRUNCATE比DELETE更快(数据量小可能体现不出来,单数据量大就很明显了) 原因:TRUNCATE是DDL(数据定义语言)DELETE是逐行删除属于(DML) TRUNCATE 不会产生大量日志,但DELETE删除会产生大量日志 DELETE FROM 可以加WHERE子句指定…

AI-数学-高中-17-三角函数的定义

原作者视频&#xff1a;三角函数】4三角函数的定义&#xff08;易&#xff09;_哔哩哔哩_bilibili 初中&#xff1a; 高中&#xff1a;三角函数就是单位圆上的点的横纵坐标(x0,y0)。 示例1&#xff1a; 规则&#xff1a; 示例2&#xff1a; 示例3.1&#xff1a; 示例3.2 示例4…

【异常检测复现】【DeSTSeg】在虚拟环境中完成配置

文章目录 1.在虚拟环境中安装git2.更改虚拟环境中包的安装位置3.安装anomalib4.安装虚拟环境5.根据requirements.txt安装包6.选择所建立的虚拟环境&#xff0c;并设置解释器7.查看文件夹的内容8.下载数据集9.引用 记录复现过程中安装各种包的命令 下载数据集到本地 1.在虚拟环境…

MySQL误操作数据后闪回恢复——Binlog2sql使用详解

Binlog2sql的用途 数据快速回滚(闪回)主从切换后新master丢数据的修复从binlog生成标准SQL&#xff0c;带来的衍生功能 Binlog2sql的安装步骤 首先&#xff0c;确保系统安装了git和pip。 安装binlog2sql。可以通过以下命令进行&#xff1a; shell> git clone https://g…

(十二)springboot实战——SSE服务推送事件案例实现

前言 SSE&#xff08;Server-Sent Events&#xff0c;服务器推送事件&#xff09;是一种基于HTTP协议的服务器推送技术。它允许服务器向客户端发送异步的、无限长的数据流&#xff0c;而无需客户端不断地轮询或发起请求。这种技术可以用来实现实时通信、在线聊天、即时更新等功…

格力软件设计岗位2024春招一面记录

本文介绍2024届秋招中&#xff0c;格力的软件开发岗位一面的面试基本情况、提问问题等。 2024年01月投递了格力的电控软件设计岗位&#xff0c;但是后来简历似乎因为被调剂&#xff0c;被送到了另一个部门&#xff1b;具体部门叫什么我也没听清楚&#xff0c;但岗位就也还是软件…

C++并发编程 -2.线程间共享数据

本章就以在C中进行安全的数据共享为主题。避免上述及其他潜在问题的发生的同时&#xff0c;将共享数据的优势发挥到最大。 一. 锁分类和使用 按照用途分为互斥、递归、读写、自旋、条件变量。本章节着重介绍前四种&#xff0c;条件变量后续章节单独介绍。 由于锁无法进行拷贝…

Day 17------C语言收尾之链表的删除、位运算、预处理、宏定义

链表 空链表&#xff1a; 注意&#xff1a;函数不能返回局部变量的地址 操作&#xff1a; 1.创建空链表 2.头插 3.尾插 4.链表遍历 5.链表的长度 free&#xff1a;释放 删除&#xff1a; 头删 void popFront(struct Node *head) { //1.p指针变量指向首节点 //2.断…

ubuntu22.04 安装部署01:禁用内核更新

一、前言 ubunut22.04系统安装以后&#xff0c;内核更新会导致各种各样的问题&#xff0c;因此锁定初始安装环境特别重要&#xff0c;下面介绍如何锁定内核更新。 二、操作方法 2.1 查看可用内核 dpkg --list | grep linux-image dpkg --list | grep linux-headers dpkg --…

NLP_NLP技术的演进史

文章目录 起源: NLP 的起源可以追溯到阿兰图灵在20 世纪50年代提出的图灵测试。图灵测试的基本思想是&#xff0c;如果一个计算机程序能在自然语言对话中表现得像一个人&#xff0c;那么我们可以说它具有智能。从这里我们可以看出&#xff0c;AI最早的愿景与自然语言处理息息相…