MyBastis 三种批量插入方式的性能比较

数据库使用的是MySQL,JDK版本1.8,运行在SpringBoot环境下

本文章源代码:https://github.com/runbeyondmove/mybatis-batch-demo

对比3种可用的方式

1、反复执行单条插入语句
2、xml拼接sql
3、批处理执行

先说结论:少量插入请使用反复插入单条数据,方便。数量较多请使用批处理方式。(可以考虑以有需求的插入数据量20条左右为界吧,在我的测试和数据库环境下耗时都是百毫秒级的,方便最重要)。无论何时都不用xml拼接sql的方式

1. xml映射文件中的代码

<insert id="insert" parameterType="top.spanrun.bootssm.model.UserInf" useGeneratedKeys="true" keyProperty="id"><!--@mbggenerated  generator自动生成,注意order的before和after--><!--<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">SELECT LAST_INSERT_ID()</selectKey>-->insert into user_inf (id, uname, passwd, gentle, email, city)values (#{id,jdbcType=INTEGER}, #{uname,jdbcType=VARCHAR}, #{passwd,jdbcType=VARCHAR}, #{gentle,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR}, #{city,jdbcType=VARCHAR})</insert><insert id="insertWithXML" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id">insert into user_inf (id, uname, passwd, gentle, email, city)values<foreach collection="list" item="user" index="index" separator=",">(#{user.id,jdbcType=INTEGER}, #{user.uname,jdbcType=VARCHAR}, #{user.passwd,jdbcType=VARCHAR},#{user.gentle,jdbcType=VARCHAR}, #{user.email,jdbcType=VARCHAR}, #{user.city,jdbcType=VARCHAR})</foreach></insert>

 

2. Mapper接口

@Mapper
public interface UserInfMapper {int insert(UserInf record);int insertWithXML(@Param("list") List<UserInf> list);
}

 

3. Service实现,接口声明省略

@Service
public class UserInfServiceImpl implements UserInfService{private static final Logger LOGGER = LoggerFactory.getLogger(UserInfServiceImpl.class);@AutowiredSqlSessionFactory sqlSessionFactory;@AutowiredUserInfMapper userInfMapper;@Transactional@Overridepublic boolean testInsertWithBatch(List<UserInf> list) {LOGGER.info(">>>>>>>>>>>testInsertWithBatch start<<<<<<<<<<<<<<");SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH,false);UserInfMapper mapper = sqlSession.getMapper(UserInfMapper.class);long startTime = System.nanoTime();try {List<UserInf> userInfs = Lists.newArrayList();for (int i = 0; i < list.size(); i++) {// 每1000条提交一次
if ((i+1)%1000 == 0){sqlSession.commit();sqlSession.clearCache();}mapper.insert(list.get(i));}} catch (Exception e) {e.printStackTrace();} finally {sqlSession.close();}LOGGER.info("testInsertWithBatch spend time:{}",System.nanoTime()-startTime);LOGGER.info(">>>>>>>>>>>testInsertWithBatch end<<<<<<<<<<<<<<");return true;}@Transactional@Overridepublic boolean testInsertWithXml(List<UserInf> list) {LOGGER.info(">>>>>>>>>>>testInsertWithXml start<<<<<<<<<<<<<<");long startTime = System.nanoTime();userInfMapper.insertWithXML(list);LOGGER.info("testInsertWithXml spend time:{}",System.nanoTime()-startTime);LOGGER.info(">>>>>>>>>>>testInsertWithXml end<<<<<<<<<<<<<<");return true;}@Transactional@Overridepublic boolean testInsertWithForeach(List<UserInf> list) {LOGGER.info(">>>>>>>>>>>testInsertWithForeach start<<<<<<<<<<<<<<");long startTime = System.nanoTime();for (int i = 0; i < list.size(); i++) {userInfMapper.insert(list.get(i));}LOGGER.info("testInsertWithForeach spend time:{}",System.nanoTime()-startTime);LOGGER.info(">>>>>>>>>>>testInsertWithForeach end<<<<<<<<<<<<<<");return true;}@Transactional@Overridepublic boolean testInsert(UserInf userInf) {LOGGER.info(">>>>>>>>>>>testInsert start<<<<<<<<<<<<<<");long startTime = System.nanoTime();LOGGER.info("insert before,id=" + userInf.getId());userInfMapper.insert(userInf);LOGGER.info("insert after,id=" + userInf.getId());LOGGER.info("testInsert spend time:{}",System.nanoTime()-startTime);LOGGER.info(">>>>>>>>>>>testInsert end<<<<<<<<<<<<<<");return true;} }

 

4. Controller控制器

@RestController
public class UserInfController {@AutowiredUserInfService userInfService;@RequestMapping(value = "test/{size}/{type}")public void testInsert(@PathVariable(value = "size") Integer size,@PathVariable(value = "type") Integer type){System.out.println(">>>>>>>>>>>>type = " + type + "<<<<<<<<<<<<<");switch (type){case 1:userInfService.testInsertWithForeach(generateList(size));break;case 2:userInfService.testInsertWithXml(generateList(size));break;case 3:userInfService.testInsertWithBatch(generateList(size));break;default:UserInf userInf = new UserInf();userInf.setUname("user_single");userInf.setGentle("1");userInf.setEmail("123@123.com");userInf.setCity("广州市");userInf.setPasswd("123456");userInfService.testInsert(userInf);}}private List<UserInf> generateList(int listSize){List<UserInf> list = Lists.newArrayList();UserInf userInf = null;for (int i = 0; i < listSize; i++) {userInf = new UserInf();userInf.setUname("user_" + i);userInf.setGentle("1");userInf.setEmail("123@123.com");userInf.setCity("广州市");userInf.setPasswd("123456");list.add(userInf);}return list;}
}

  

测试结果(单位是纳秒):

1000
testInsertWithForeach spend time:431526521
testInsertWithXml     spend time:118772867
testInsertWithBatch   spend time:17560234610000
testInsertWithForeach spend time:2072525050
testInsertWithXml     spend time:685605121
testInsertWithBatch   spend time:894647254100000
testInsertWithForeach spend time:18950160161
testInsertWithBatch   spend time:8469312537testInsertWithXml报错
### Cause: com.mysql.jdbc.PacketTooBigException: Packet for query is too large (9388970 > 4194304). You can change this value on the server by setting the max_allowed_packet' variable.
; Packet for query is too large (9388970 > 4194304). You can change this value on the server by setting the max_allowed_packet' variable.; nested exception is com.mysql.jdbc.PacketTooBigException: Packet for query is too large
(9388970 > 4194304). You can change this value on the server by setting the max_allowed_packet' variable.] with root causecom.mysql.jdbc.PacketTooBigException: Packet for query is too large (9388970 > 4194304). You can change this value on the server by setting the max_allowed_packet' variable.

  查看xml sql拼接的异常信息,可以发现,最大只能达到4194304,也就是4M,所以这种方式不推荐

结论

循环插入单条数据虽然效率极低,但是代码量极少,如果在使用tk.Mapper的插件情况下,仅需代码,:

@Transactional
public void add1(List<Item> itemList) {itemList.forEach(itemMapper::insertSelective);
}

因此,在需求插入数据数量不多的情况下肯定用它了。

xml拼接sql是最不推荐的方式,使用时有大段的xml和sql语句要写,很容易出错,工作效率很低。更关键点是,虽然效率尚可,但是真正需要效率的时候你挂了,要你何用?

批处理执行是有大数据量插入时推荐的做法,使用起来也比较方便。

 

其他在使用中的补充:

1. 使用mybatis generator生成器生成中的一些坑

代码说明:数据库是MySQL,且主键自增,用generator 生成的mapper.xml中的代码,自增ID,使用的是selectKey来获取。

问题描述:insert的时候,添加的时候,第一条数据添加成功,接着添加第二条数据的时候会提示失败,失败的原因是ID还是使用的上一个ID值,主键重复导致插入失败。异常如下:

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '4' for key 'PRIMARY'

问题原因:BEFORE还是AFTER的问题

<selectKey keyProperty="id" order="BEFORE" resultType="java.lang.Integer">SELECT LAST_INSERT_ID()
</selectKey>

需要注意的是,Oracle使用before,MySQL使用after

其实在使用Mybatis generator生成带代码的时候可以通过identity="true"来指定生成的selectKey是before还是after

<generatedKey column="id" sqlStatement="Mysql" identity="true" />

注:在select标签中使用useGeneratedKeys="true" keyProperty="id" 不存在该问题

 

2. mybatis的版本

升级Mybatis版本到3.3.1

 

3. 在批量插入的拼接xml sql时注意foreach是没有使用open和close的,但是在批量查询修改删除时才使用到open和close

<foreach collection="list" item="user" index="index" separator=",">(#{user.id,jdbcType=INTEGER}, #{user.uname,jdbcType=VARCHAR}, #{user.passwd,jdbcType=VARCHAR},#{user.gentle,jdbcType=VARCHAR}, #{user.email,jdbcType=VARCHAR}, #{user.city,jdbcType=VARCHAR})</foreach>

  

 4. 使用批量提交注意的事项

  a. 事务

    由于在 Spring 集成的情况下,事务连接由 Spring 管理(SpringManagedTransaction),所以这里不需要手动关闭 sqlSession,在这里手动提交(commit)或者回滚(rollback)也是无效的。

   b. 批量提交

    批量提交只能应用于 insert, update, delete。

    并且在批量提交使用时,如果在操作同一SQL时中间插入了其他数据库操作,就会让批量提交方式变成普通的执行方式,所以在使用批量提交时,要控制好 SQL 执行顺序

 

转载于:https://www.cnblogs.com/move22/p/9811726.html

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

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

相关文章

JS对象与jQuery对象

JS对象大致可以分为三种&#xff0c;如下图&#xff1a; JS常用内置对象&#xff08;JS自身所持有的对象&#xff0c;不需要创建&#xff0c;直接可用&#xff09;&#xff1a; String&#xff1a;API跟java的字符串API大致相同 两种创建对象的方式&#xff1a;String s1 “…

Vue-router 中hash模式和history模式的区别

Vue-router 中hash模式和history模式的关系 在vue的路由配置中有mode选项 最直观的区别就是在url中 hash 带了一个很丑的 # 而history是没有#的 mode:"hash"; mode:"history"; hash模式和history模式的不同 对于vue这类渐进式前端开发框架&#xff0…

Nginx Slab内存管理

L38 Slub内存管理适用 ngx_http_limit_conn_module、ngx_http_limit_req_module 模块场景 我们可以用阿里第三方模块Slab_Stat模块 并且用add-module 方式编译进openresty中 转载于:https://www.cnblogs.com/jackey2015/p/10684151.html

day2---while else

# -*- coding:utf-8 -*-while 条件&#xff1a;循环体终止循环 else&#xff1a; while else 解释&#xff1a;当循环体中没有被break打断则会运行else&#xff0c;打断则不会运行else# 例子 a 0 while a < 5:print(a)a 1break else:print(循环结束) # 0 转载于:https:/…

jQuery中this与$(this)的区别总结

https://www.cnblogs.com/gfl123/p/8080484.html

2019前端必会黑科技之PWA

一、背景 从2018年到现在&#xff0c;作为号称下一代web应用模型的PWA&#xff0c;逐渐成为了一个各大前端厂商争先恐后进行涉足&#xff0c;布局的一个新的技术&#xff0c; 其主要的对标物Native app&#xff0c;作为现在最主流的mobile端应用&#xff0c;它的安全&#xff…

Tcpdump抓包工具的使用

# Tcpdump抓包工具的使用## 简介tcpdump是linux下最常用的命令行抓包工具&#xff0c;可以在线安装## 安装- sudo apt install tcpdump## 查看网卡- ip addr查看网卡名称## 简单的使用示例- sudo tcpdump -i enp032 抓取指定网卡的数据包&#xff0c;并- sudo tcpdump -i enp03…

Node.js异步库async

async的使用需要安装第三方包 1.串行无关联 async.series 函数依次执行,后面不需要调前面步骤的结果 程序执行时间为所有步骤之和 2.并行无关联 async.paraller 某步出错不影响其他步骤执行 程序执行时间为最长的那个时间 3.串行有关联 async.waterfall 函数依次执行,后面需要…

Java技术栈---语言基础

基础语法 面向对象 接口 容器 异常 泛型 反射 注解 I/O

Mongodb 查询时间类型

$where: this.CreateDate.toJSON().slice(0,13) "2019-04-04T05"转载于:https://www.cnblogs.com/kevin1988/p/10685075.html

vue prop不同数据类型(数组,对象..)设置默认值

vue prop 会接收不同的数据类型&#xff0c;这里列出了 常用的数据类型的设置默认值的写法,其中包含&#xff1a; Number, String, Boolean, Array, Function, Object refAge: { type: Number, default: 0 }, refName: { type: String, default: }, hotDataLoading: { typ…

正则表达式——基础

正则表达式的基本符号使用&#xff1a; 1。基本符号&#xff1a; a . 匹配任意单个字符&#xff0c;如&#xff1a;.000就可以匹配到1000&#xff0c;2000&#xff0c;3000&#xff0c;a000,b000等。 b | 匹配满足其中一个条件&#xff0c;如&#xff1a; 1000|2000|3000 可以…

谈一谈并查集QAQ(上)

最近几日理了理学过的很多oi知识。。。发现不知不觉就有很多的知识忘记了。。。 在聊聊并查集的时候顺便当作巩固吧。。。。 什么是并查集呢? ( Union Find Set ) 是一种用于处理分离集合的抽象数据结构类型。 具体一点: 当我们给出两个元素的一个无序对&#xff08;a,b&#…

vue的双向绑定原理及实现

前言 使用vue也好有一段时间了&#xff0c;虽然对其双向绑定原理也有了解个大概&#xff0c;但也没好好探究下其原理实现&#xff0c;所以这次特意花了几晚时间查阅资料和阅读相关源码&#xff0c;自己也实现一个简单版vue的双向绑定版本&#xff0c;先上个成果图来吸引各位&a…

python后端将svc文件数据读入数据库具体实现

如何用python将svc文件的数据读入到MySQL数据库里&#xff0c;在此直接上代码了&#xff0c;感兴趣的朋友可以贴代码测试&#xff1a; import pandas as pd import os from sqlalchemy import create_engine # 初始化数据库连接&#xff0c;使用pymysql模块 # MySQL的用户&…

作业——8

这个作业属于哪个课程C语言程序设计Ⅱ这个作业的要求在哪里C语言作业评价标准我在这个课程的目标是指针与字符串这个作业在哪个具体方面帮助我实现目标使用指针与字符串参考文献指针和字符串&#xff08;基础知识&#xff09;第七周作业 一 1 、使用函数删除字符串中的字符 输入…

Vue实现组件props双向绑定解决方案

注意&#xff1a; 子组件不能直接修改prop过来的数据&#xff0c;会报错 方案一&#xff1a; 用data对象中创建一个props属性的副本 watch props属性 赋予data副本 来同步组件外对props的修改 watch data副本&#xff0c;emit一个函数 通知到组件外 HelloWorld组件代码如下…

统计词频问题

adict{} xinput().lower() #把单词大写字母改为小写字母 for i in x:if i in [,,.,"",",!]:xx[:x.index(i)]x[x.index(i)1:] #把句子中的非字母字符用切片操作删掉 asetset(x.split( )) #集合的好处在于不重复 alstx.split( ) for n in aset:tempdict{n:alst.…

正则表达式常用函数

<?php //preg_match("正则表达式","字符串")用于在字符串中查找匹配项 $email "987044391qq.com"; if (preg_match("/^([a-zA-Z0-9])([.a-zA-Z0-9_-])*([.a-zA-Z0-9_-])([.a-zA-Z0-9_-])([.a-zA-Z0-9_-])$/",$email)){ echo 匹…

利用js的闭包原理做对象封装及调用方法

创建一个js文件&#xff0c;名为testClosure.js&#xff1a; ? 1 2 3 4 5 6 7 8 9 (function () { function a() { alert(i am a); } outFunc function () { a(); } })(); 这里不论写多少个function,a b c d ...外面都调用不到&#xff0c;包括这里面va…