mybatis和mybatisplus批量插入問題示例詳解
1. 思路分析:
批量插入是我們?nèi)粘i_放經(jīng)常會(huì)使用到的場(chǎng)景,一般情況下我們也會(huì)有兩種方案進(jìn)行實(shí)施,如下所示。
方案一 就是用 for 循環(huán)循環(huán)插入:
優(yōu)點(diǎn):JDBC 中的 PreparedStatement 有預(yù)編譯功能,預(yù)編譯之后會(huì)緩存起來,后面的 SQL 執(zhí)行會(huì)比較快并且JDBC 可以開啟批處理,這個(gè)批處理執(zhí)行非常給力。
缺點(diǎn):很多時(shí)候我們的 SQL 服務(wù)器和應(yīng)用服務(wù)器可能并不是同一臺(tái),所以必須要考慮網(wǎng)絡(luò) IO,如果網(wǎng)絡(luò) IO 比較費(fèi)時(shí)間的話,那么可能會(huì)拖慢
SQL 執(zhí)行的速度。
再來說第二種方案,就是生成一條 SQL 插入:
優(yōu)勢(shì):這種方案的優(yōu)勢(shì)在于只有一次網(wǎng)絡(luò) IO,即使分片處理也只是數(shù)次網(wǎng)絡(luò) IO,所以這種方案不會(huì)在網(wǎng)絡(luò) IO 上花費(fèi)太多時(shí)間。
缺點(diǎn)一是 SQL 太長(zhǎng)了,甚至可能需要分片后批量處理;
缺點(diǎn)二是無法充分發(fā)揮 PreparedStatement 預(yù)編譯的優(yōu)勢(shì),SQL 要重新解析且無法復(fù)用;三是最終生成的 SQL
太長(zhǎng)了,數(shù)據(jù)庫管理器解析這么長(zhǎng)的 SQL 也需要時(shí)間。
2. rewriteBatchedStatements=true
在jdbc連接后面加上 rewriteBatchedStatements=true ,加上后才是真正的批量插入。
jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&rewriteBatchedStatements=true
3.使用mybatis批量插入:
方案一:使用foreach進(jìn)行插入(生成一條 SQL 插入)
mapper文件
<insert id="save" parameterType="java.util.List"> INSERT INTO test ( id, a, b, c ) VALUES <foreach collection="list" item="item" index="index" separator=","> ( #{item.id}, #{item.a}, #{item.b}, #{item.c} ) </foreach> </insert>
調(diào)用方法
@Override public void add() { //時(shí)間 一 long l = System.currentTimeMillis(); List<TestEntity> list=new ArrayList<>(); for (int i=0;i<1000;i++){ TestEntity testEntity=new TestEntity(); testEntity.setC(i); list.add(testEntity); } testMapper.save(list); //時(shí)間 二 long l1 = System.currentTimeMillis(); System.out.println("耗時(shí)"+(l1-l)); }
插入了1000條數(shù)據(jù),耗時(shí)535毫秒。
插入了50000條數(shù)據(jù),直接報(bào)錯(cuò)。
報(bào)錯(cuò)原因是因?yàn)槲覀円粭lSQL進(jìn)行插入導(dǎo)致SQL太長(zhǎng)
解決辦法:
1.修改MySQL配置
2.對(duì)新增數(shù)據(jù)進(jìn)行分片
方案二:一條條插入
mapper
<insert id="addUserOneByOne" parameterType="com.ruoyi.system.domain.TestEntity"> insert into test (id,a,b,c) values (#{id},#{a},#,#{c}) </insert>
測(cè)試代碼
@Service public class TestServiceimpl extends ServiceImpl<TestMapper, TestEntity> implements TestService { @Autowired private TestMapper testMapper; @Autowired private SqlSessionFactory sqlSessionFactory; public void addUserOneByOne(List<TestEntity> users) { SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH); TestMapper um = session.getMapper(TestMapper.class); long startTime = System.currentTimeMillis(); for (TestEntity user : users) { um.addUserOneByOne(user); } session.commit(); long endTime = System.currentTimeMillis(); System.out.println("耗時(shí)"+(endTime - startTime)); } }
插入了1000條數(shù)據(jù),耗時(shí)959毫秒。
插入50000條數(shù)據(jù),耗時(shí)11214毫秒。
對(duì)比分析:
如果我們批量插入少部分?jǐn)?shù)據(jù),可以使用方式一,一條SQL進(jìn)行插入。這樣是比較快的。
如果我們插入數(shù)據(jù)達(dá)到,1w條,10來萬條,這時(shí)建議用方式二進(jìn)行插入是比較快的。
4. 使用mybatisplus批量插入
使用saveBatch()方法進(jìn)行批量插入
@Service public class TestServiceimpl extends ServiceImpl<TestMapper, TestEntity> implements TestService { @Autowired private TestMapper testMapper; @Autowired private SqlSessionFactory sqlSessionFactory; @Override public void add() { //時(shí)間 一 long l = System.currentTimeMillis(); List<TestEntity> list=new ArrayList<>(); for (int i=0;i<50000;i++){ TestEntity testEntity=new TestEntity(); testEntity.setC(i); list.add(testEntity); } saveBatch(list); //時(shí)間 二 long l1 = System.currentTimeMillis(); System.out.println("耗時(shí)"+(l1-l)); }
插入50000條數(shù)據(jù),耗時(shí)19516毫秒
源碼分析
public boolean saveBatch(Collection<T> entityList, int batchSize) { String sqlStatement = this.getSqlStatement(SqlMethod.INSERT_ONE); return this.executeBatch(entityList, batchSize, (sqlSession, entity) -> { sqlSession.insert(sqlStatement, entity); }); }
這里注意 return 中的第三個(gè)參數(shù),是一個(gè) lambda 表達(dá)式,這也是 MP 中批量插入的核心邏輯,可以看到,MP 先對(duì)數(shù)據(jù)進(jìn)行分片(默認(rèn)分片大小是 1000),分片完成之后,也是一條一條的插入。
public static <E> boolean executeBatch(Class<?> entityClass, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) { Assert.isFalse(batchSize < 1, "batchSize must not be less than one", new Object[0]); return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, (sqlSession) -> { int size = list.size(); int i = 1; for(Iterator var6 = list.iterator(); var6.hasNext(); ++i) { E element = var6.next(); consumer.accept(sqlSession, element); if (i % batchSize == 0 || i == size) { sqlSession.flushStatements(); } } }); }
繼續(xù)查看 executeBatch 方法,就會(huì)發(fā)現(xiàn)這里的 sqlSession 其實(shí)也是一個(gè)批處理的 sqlSession,并非普通的 sqlSession。和我們mybatis使用的方法二一致。
5業(yè)務(wù)場(chǎng)景一對(duì)多怎么處理:
比如,如下這種一對(duì)多場(chǎng)景。
新增的時(shí)候保存都好理解,形成一個(gè)數(shù)組一起保存。
而修改的時(shí)候就有點(diǎn)難處理了,比如我修改了第二條,刪除了第三條,這時(shí)統(tǒng)一保存應(yīng)該怎么處理?
使用 ON DUPLICATE KEY UPDATE (發(fā)生主鍵沖突就更新,沒有發(fā)生主鍵沖突就新增)
有時(shí)候由于業(yè)務(wù)需求,可能需要先去根據(jù)某一字段值查詢數(shù)據(jù)庫中是否有記錄,有則更新,沒有則插入。這個(gè)時(shí)候就可以用到ON DUPLICATE key update這個(gè)sql語句了
mapper如下所示
<insert id="save" parameterType="java.util.List"> INSERT INTO test ( id, a, b, c ) VALUES <foreach collection="list" item="item" index="index" separator=","> ( #{item.id}, #{item.a}, #{item.b}, #{item.c} ) </foreach> ON DUPLICATE KEY UPDATE id=id, a = VALUES(a) , b = VALUES(b), c = VALUES(c) </insert>
或者在使用mybatisplus時(shí),使用saveOrUpdate()方法進(jìn)行一條數(shù)據(jù)的新增或更新。 saveOrUpdateBatch()方法進(jìn)行批量數(shù)據(jù)的新增或更新。
總結(jié)
到此這篇關(guān)于mybatis和mybatisplus批量插入問題的文章就介紹到這了,更多相關(guān)mybatis mybatisplus批量插入問題內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IDEA中Javaweb項(xiàng)目圖片加載不出來解決方案
在IDEA中能夠正常的預(yù)覽到圖片,但是在生成項(xiàng)目的war包時(shí),項(xiàng)目的目錄結(jié)構(gòu)卻會(huì)發(fā)生變化,所以無法訪問圖片,本文主要介紹了IDEA中Javaweb項(xiàng)目圖片加載不出來解決方案,感興趣的可以了解一下2023-10-10Spring Boot Admin Server管理客戶端過程詳解
這篇文章主要介紹了Spring Boot Admin Server管理客戶端過程詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03java比較器Comparable接口與Comaprator接口的深入分析
本篇文章是對(duì)java比較器Comparable接口與Comaprator接口進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06Java結(jié)構(gòu)型設(shè)計(jì)模式之組合模式Composite Pattern詳解
組合模式,又叫部分整體模式,它創(chuàng)建了對(duì)象組的數(shù)據(jù)結(jié)構(gòu)組合模式使得用戶對(duì)單個(gè)對(duì)象和組合對(duì)象的訪問具有一致性。本文將通過示例為大家詳細(xì)介紹一下組合模式,需要的可以參考一下2022-11-11springboot中設(shè)置定時(shí)任務(wù)的三種方法小結(jié)
在我們開發(fā)項(xiàng)目過程中,經(jīng)常需要定時(shí)任務(wù)來幫助我們來做一些內(nèi)容,本文介紹了springboot中設(shè)置定時(shí)任務(wù)的三種方法,主要包括@Scheduled注解,Quartz框架和xxl-job框架的實(shí)現(xiàn),感興趣的可以了解一下2023-12-12