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.pswp.cn/news/249205.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/249205.shtml
英文地址,請注明出處:http://en.pswp.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…