Mybatis 批量插入数据 SQL

100 篇文章 14 订阅
11 篇文章 0 订阅

温故而知新,这里记录一下

案例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

  • 20
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SqlToolBox是一款纯绿色的免费数据库客户端软件,基于Java Swing编制而成,旨在于为开发人员,系统工程师和数据库管理员提供一种通用方便和快捷的数据库操作工具,使他们摆脱需要学习掌握使用多种数据库客户端的苦恼,并减轻他们日常操作数据库和编写Sql语句的任务量,帮助他们把精力投入到解决更有意义的问题上去。 SqlToolBox现有功能 1. 能连接到MySql,Oracle和Ms Sql Server三种数据库。 2. 连接到数据库后,会提供数据库Schema和表的树视图以便用户进行浏览和查找,另外还提供了一个过滤器帮助用户缩小查找范围。 3. 用户能自动快速获取单表的创建,查询,更新,删除,建表语句,整表全部数据插入语句,单表对应Pojo类和单表的Hibernate映射文件等常用文字,且可借此构造更复杂的Sql语句。 4. 能执行Sql语句并显示执行结果,如果是查询语句会以表格形式显示结果,还提供CSV形式数据下载;如果是非查询语句或是错误的查询语句则会以文字形式告知用户。 5. 在用户输入Sql语句的过程中提供Sql语法高亮功能,以助于Sql语句的识别。 6. 提供Sql格式化功能以助于Sql语句的识别和整理。 7. 提供Redo/Undo,Shift整体退格进格,大小写转化,将Sql语句用StringBuilder包容以及将Sql语句中关键字大写表示等常用文字编辑功能。这些都能帮助程序员在程序中书写Sql语句。 8. 能保存和记忆数据库信息,以便下次打开。 。 运行SqlToolBox有何前提条件? 将SqlToolBox运行起来的唯一前提是安装JDK6或以上版本。 SqlToolBox需要安装吗? SqlToolBox是一款纯绿色软件,它对您的系统不做出任何更改,因此不需要安装和卸载。 SqlToolBox安全吗? 由于软件使用Java编写而成,它本身就具有较高的安全性。此外作者保证在SqlToolBox整个系列中都不会加入病毒,木马,插件等坏东西。 如何运行SqlToolBox? 解开下载包,然后双击run.bat即可。 在Unix/Linux下如何运行SqlToolBox? 除了也需要安装JDK外,您还需要参照run.bat写一份脚本,然后执行它。 如何使用SqlToolBox打开一个数据库? 程序运行起来后,您将看到一个输入数据库信息的对话框,请依次填入数据库所在机器的IP地址,数据库的库名称,选择数据库的类型以及输入登录数据库的用户名和密码等必要信息。此后再点击“连接数据库”按钮,程序将打开数据库。如果您将以上信息填错也不要紧,程序会提示哪里出现了问题。此外您可以在登录前点击“测试连接”按钮,程序也会告诉您是否能连接到指定的数据库。 打开数据库后程序左边部分如何使用? 成功连接到数据库以后,数据库的Schema和table结构会在画面的左边以树的形式展现出来,如果展现的内容过多,您还可以在上方的“过滤器”输入栏中输入关键字以缩小展现范围。在这颗树中,表格(table)是以小圆点的方式展现的,左键点击这个圆点,程序将在右侧打开一个Sql语句操作窗口,并执行“select * from table”语句,最后在下方以表格的形式展现给您;如果您用右键点击这个圆点,程序将弹出一个右键菜单,选择其中的项目您将可以在右边的Sql语句操作窗口中得到单表的字段信息,创建(insert),查询(select),更新(update),删除语句(delete)及建表语句(create table),单表对应Pojo文件,单表的Hibernate映射文件等文字。 打开数据库后程序右边部分是如何使用的? 用左右键点击表格后,您将在右侧看到一个“Sql语句操作窗口”,它分成三部分:工具栏菜单,输入窗口和输出窗口。输入窗口是用以输入,编辑和整理Sql 语句的;工具栏菜单中的一大排按钮都是为编辑处理输入窗口中的文字而准备的;输出窗口则是展示Sql语句执行后的结果的,如果是查询语句,它会以表格的形式告知您查询的结果,如果是其它语句,它会以文字的形式告知。通常的操作手法是,先在输入窗口中用鼠标选中您要操作的文本,再在工具栏菜单中点击执行特定操作的按钮,然后在下方的输出窗口中就能看到具体的结果,当然如果仅是文本编辑操作的话输出窗口是不会有反应的。 如何执行Sql语句? 程序员和数据库管理员总是习惯使用语句来操作数据库,这也是本软件的最重要功能之一。执行Sql语句的过程具体来说是这样做的,首先,在输入窗口输入您向执行的Sql语句,如“select * from table”之类,当然您更可以通过表格的右键菜单来获得常用的sql语句(在输入或是粘贴文本的过程中,Sql语句中的关键字会以蓝色显示,这是语法高亮功能所致);其次,你需要选中你想执行的文本,再点击工具栏菜单中的向右三角形按钮,这段文本将得到执行,执行结果将在下方的输出窗口得到展示。如果您执行的是查询语句,输出窗口将以表格的形式列出查询结果集的字段和内容;如果您执行的是删除,更新,添加,修改表等语句或是执行错误的Sql文本,输出窗口将以文本形式告知执行结果。另外工具栏菜单中的双向右三角形按钮用于批量执行Sql语句,它以分号“;”来作为每段Sql的分隔标志,然后分别执行每段。 如何快速调整对执行查询语句后得到的表格列宽度? 如果您想自动调整某列的宽度,可以双击这列的表头,此后这列的宽度会根据这列的最长文字进行调整;您还可以在表格上点击右键,选择“调整列宽为最适合状态”一项,那么所有的列宽都会进行调整。 如何得到执行查询语句后得到的表格的内容? 您还可以在表格上点击右键,选择“下载表格为CSV文件”一项,此后查询语句和得到的结果都会被放入一个CSV文件中。CSV是一中文本文件,但您可以用Excel打开它,也会得到xls文件一样的效果。 在新增或是删除一张表后,在左边的树中为什么没有相应的变化? 新增或是删除一张表后,您需要手动执行一下左上方的更新按钮(最上方的大图标中第一个),此后程序会重新载入数据库的Schema和table,这样您刚才对表格进行增删操作就能体现出来。 如果我需要常打开数据库进行操作或是需要常操作多个数据库,程序能为我提供那些便利? 本软件有记忆功能,如果您正确连接到一个数据库,那么相应的信息如IP地址,数据库名,数据库类型,连接数据库的用户名和密码都会被记忆下来,这样下次打开时就不用重复输入了。如果您需要常操作多个数据库,您可以通过保存按钮(最上方五个大图标中的第二个)将数据库信息保存成XML文件,这样在登录画面中就可以通过“打开文件按钮”得到相应的数据库信息。
书籍1:互联网 Java 工程师面试题 内容涵盖:Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、 Redis、MySQL、Spring、Spring Boot、Spring Cloud、RabbitMQ、Kafka、 Linux 等技术栈 1、MyBatis 面试题 1、什么是 Mybatis 2、Mybaits 的优点 3、MyBatis 框架的缺点 4、MyBatis 框架适用场合 5、MyBatis 与 Hibernate 有哪些不同? 6、#{}和${}的区别是什么? 7、当实体类中的属性名和表中的字段名不一样 ,怎么办 ? 8、 模糊查询 like 语句该怎么写? 9、通常一个 Xml 映射文件,都会写一个 Dao 接口与之对应,请问,这个 Dao 接口的工作原理是什么?Dao 接口里的方法,参数不同时,方法能重载吗? 10、Mybatis 是如何进行分页的?分页插件的原理是什么? 11、Mybatis 是如何将 sql 执行结果封装为目标对象并返回的?都有哪些映射形式? 12、如何执行批量插入? 13、如何获取自动生成的(主)键值? 14、在 mapper 中如何传递多个参数? 15、Mybatis 动态 sql 有什么用?执行原理?有哪些动态 sql? 16、Xml 映射文件中,除了常见的 select|insert|updae|delete 标签之外,还有哪些标签? 18、为什么说 Mybatis 是半自动 ORM 映射工具?它与全自动的区别在哪里? 19、 一对一、一对多的关联查询 ? 20、MyBatis 实现一对一有几种方式?具体怎么操作的? 21、MyBatis 实现一对多有几种方式,怎么操作的? 22、Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么? 23、Mybatis 的一级、二级缓存 24、什么是 MyBatis 的接口绑定?有哪些实现方式? 25、使用 MyBatis 的 mapper 接口调用时有哪些要求? 26、Mapper 编写有哪几种方式? 27、简述 Mybatis 的插件运行原理,以及如何编写一个插件 等等 书籍2:JAVA核心知识整理

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值