SpringBoot整合mybatisPlus實現(xiàn)批量插入并獲取ID詳解
背景:需要實現(xiàn)批量插入并且得到插入后的ID。
使用for循環(huán)進行insert這里就不說了,在海量數(shù)據(jù)下其性能是最慢的。數(shù)據(jù)量小的情況下,沒什么區(qū)別。
【1】saveBatch(一萬條數(shù)據(jù)總耗時:2478ms)
mybatisplus擴展包提供的:com.baomidou.mybatisplus.extension.service.IService#saveBatch(java.util.Collection<T>)
測試代碼:
@Test public void testBatch1(){ List<SysFile> list=new ArrayList<>(); list.add(new SysFile().setFileName("fiel1")); list.add(new SysFile().setFileName("fiel2")); list.add(new SysFile().setFileName("fiel3")); list.add(new SysFile().setFileName("fiel4")); list.add(new SysFile().setFileName("fiel5")); list.add(new SysFile().setFileName("fiel6")); fileService.saveBatch(list); System.out.println(list); }
我們分析其實現(xiàn)原理如下:com.baomidou.mybatisplus.extension.service.impl.ServiceImpl#saveBatch
@Transactional(rollbackFor = Exception.class) @Override public boolean saveBatch(Collection<T> entityList, int batchSize) { String sqlStatement = sqlStatement(SqlMethod.INSERT_ONE); int size = entityList.size(); executeBatch(sqlSession -> { int i = 1; for (T entity : entityList) { sqlSession.insert(sqlStatement, entity); if ((i % batchSize == 0) || i == size) { sqlSession.flushStatements(); } i++; } }); return true; }
其實也就是一條條插入。
【2】集合方式foreach(一萬條數(shù)據(jù)總耗時:474ms)
SysFileMapper 自定義方法batchSaveFiles
public interface SysFileMapper extends BaseMapper<SysFile> { int batchSaveFiles(List<SysFile> entityList); }
xml實現(xiàn)
<insert id="batchSaveFiles"> insert into tb_sys_file (file_name) values <foreach collection="list" item="item" separator=","> (#{item.fileName}) </foreach> </insert>
測試代碼:
@Test public void testBatch2(){ List<SysFile> list=new ArrayList<>(); list.add(new SysFile().setFileName("fiel1")); list.add(new SysFile().setFileName("fiel2")); list.add(new SysFile().setFileName("fiel3")); list.add(new SysFile().setFileName("fiel4")); list.add(new SysFile().setFileName("fiel5")); list.add(new SysFile().setFileName("fiel6")); fileMapper.batchSaveFiles(list); System.out.println(list); }
測試結(jié)果:
注意:這種方式得不到ID哦!
【3】MyBatis-Plus提供的InsertBatchSomeColumn方法(一萬條數(shù)據(jù)總耗時:690ms)
這里mybatisplus版本是3.3.0。
編寫MySqlInjector
public class MySqlInjector extends DefaultSqlInjector {
@Override public List<AbstractMethod> getMethodList(Class<?> mapperClass) { List<AbstractMethod> methodList = super.getMethodList(mapperClass); //更新時自動填充的字段,不用插入值 methodList.add(new InsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.UPDATE)); return methodList; } }
為什么這里不用下面第二行的方式呢?
methodList.add(new InsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.UPDATE)); methodList.add(new InsertBatchSomeColumn());
這兩行代碼分別添加了兩個 InsertBatchSomeColumn 方法到 methodList 中。
第一個 InsertBatchSomeColumn 方法使用了一個 Lambda 表達(dá)式作為參數(shù),該表達(dá)式用于過濾字段,只保留那些 getFieldFill 屬性不是 FieldFill.UPDATE 的字段。
第二個 InsertBatchSomeColumn 方法沒有參數(shù),表示不進行任何過濾,直接插入所有字段。
注入到配置類
@EnableTransactionManagement @MapperScan({"com.enodeb.mapper"}) @Configuration public class MybatisPlusConfig { @Bean public MySqlInjector sqlInjector() { return new MySqlInjector(); } }
SysFileMapper 自定義方法
public interface SysFileMapper extends BaseMapper<SysFile> { int insertBatchSomeColumn(List<SysFile> entityList);
測試代碼:
@Test public void testBatch3(){ List<SysFile> list=new ArrayList<>(); list.add(new SysFile().setFileName("fiel1")); list.add(new SysFile().setFileName("fiel2")); list.add(new SysFile().setFileName("fiel3")); list.add(new SysFile().setFileName("fiel4")); list.add(new SysFile().setFileName("fiel5")); list.add(new SysFile().setFileName("fiel6")); fileMapper.insertBatchSomeColumn(list); System.out.println(list); }
測試結(jié)果
這里不僅實現(xiàn)了【2】的效果,還可以得到插入后的ID。
【4】假設(shè)一萬條/十萬條數(shù)據(jù)的情況下,執(zhí)行時間是多少
策略 | 一萬條 | 十萬條 |
---|---|---|
方式一 | 2478ms | 20745ms |
方式二 | 474ms | 2904ms |
方式三 | 690ms | 8339ms |
① 方式一
@Test public void testBatch1(){ long start=System.currentTimeMillis(); List<SysFile> list=new ArrayList<>(); SysFile sysFile; for(int i=0;i<10000;i++){ sysFile=new SysFile(); sysFile.setFileName("file"+i); list.add(sysFile); } fileService.saveBatch(list); long end=System.currentTimeMillis(); System.out.println("一萬條數(shù)據(jù)總耗時:"+(end-start)+"ms"); }
一萬條數(shù)據(jù)總耗時:2478ms
十萬條數(shù)據(jù)總耗時:20745ms
② 方式二
@Test public void testBatch2(){ long start=System.currentTimeMillis(); List<SysFile> list=new ArrayList<>(); SysFile sysFile; for(int i=0;i<10000;i++){ sysFile=new SysFile(); sysFile.setFileName("file"+i); list.add(sysFile); } fileMapper.batchSaveFiles(list); long end=System.currentTimeMillis(); System.out.println("一萬條數(shù)據(jù)總耗時:"+(end-start)+"ms"); }
一萬條數(shù)據(jù)總耗時:474ms
十萬條數(shù)據(jù)總耗時:2904ms
③ 方式三
@Test public void testBatch3(){ long start=System.currentTimeMillis(); List<SysFile> list=new ArrayList<>(); SysFile sysFile; for(int i=0;i<10000;i++){ sysFile=new SysFile(); sysFile.setFileName("file"+i); list.add(sysFile); } fileMapper.insertBatchSomeColumn(list); long end=System.currentTimeMillis(); System.out.println("一萬條數(shù)據(jù)總耗時:"+(end-start)+"ms"); }
一萬條數(shù)據(jù)總耗時:690ms
十萬條數(shù)據(jù)總耗時:8339ms
【5】百萬條數(shù)據(jù)的情況下進行優(yōu)化
方式二、方式三都是拼接為一條SQL,也就說有多少直接全部一次性插入,這就可能會導(dǎo)致最后的 sql 拼接語句特別長,超出了mysql 的限制。
這是什么意思呢?以MySQL為例,我們是需要考慮 max_allowed_packet 這個屬性配置大小。其決定了你最大可以單次發(fā)送包的大小,這里可以修改為64M也就是 67108864。
但是這個不是最優(yōu)解,最優(yōu)解應(yīng)該是控制每次插入的數(shù)量,比如一萬條插入一次。
@Test public void testBatch4(){ List<SysFile> list=new ArrayList<>(); SysFile sysFile; for(int i=0;i<100000;i++){ sysFile=new SysFile(); sysFile.setFileName("file"+i); list.add(sysFile); } //設(shè)置每批次插入多少條數(shù)據(jù) int batchSize=10000; int count = (list.size() + batchSize - 1) / batchSize; // 計算總批次數(shù)量,確保最后一個批次也能處理 //保存單批提交的數(shù)據(jù)集合 List<SysFile> oneBatchList = new ArrayList<>(batchSize); // 預(yù)分配容量 ??????? for (int i = 0; i < count; i++) { int startIndex = i * batchSize; int endIndex = Math.min(startIndex + batchSize, list.size()); oneBatchList.addAll(list.subList(startIndex, endIndex)); fileMapper.insertBatchSomeColumn(oneBatchList); oneBatchList.clear(); // 清空集合以備下次循環(huán)使用 } }
【TIPS】
為了確保批量插入的高效性,還需要進行一些配置和優(yōu)化。例如,在application.yml中配置數(shù)據(jù)庫連接時,可以開啟MySQL的批處理模式
【rewriteBatchedStatements=true】:
spring: datasource: url: jdbc:mysql://127.0.0.1:3306/testBtach?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver
此外還可以考慮使用jdbcTemplate.batchUpdate、Spring Batch來實現(xiàn)(這兩種未測試)。
到此這篇關(guān)于SpringBoot整合mybatisPlus實現(xiàn)批量插入并獲取ID詳解的文章就介紹到這了,更多相關(guān)SpringBoot整合mybatisPlus插入ID內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中綴表達(dá)式轉(zhuǎn)后綴表達(dá)式實現(xiàn)方法詳解
這篇文章主要介紹了Java中綴表達(dá)式轉(zhuǎn)后綴表達(dá)式實現(xiàn)方法,結(jié)合實例形式分析了Java中綴表達(dá)式轉(zhuǎn)換成后綴表達(dá)式的相關(guān)算法原理與具體實現(xiàn)技巧,需要的朋友可以參考下2019-03-03Java中this和super的區(qū)別及this能否調(diào)用到父類使用
這篇文章主要介紹了Java中this和super的區(qū)別及this能否調(diào)用到父類使用,this和super都是Java中常見的關(guān)鍵字,下文關(guān)于兩者區(qū)別介紹,需要的小伙伴可以參考一下2022-05-05Java基礎(chǔ)學(xué)習(xí)之標(biāo)簽
在Java中,標(biāo)簽必須在循環(huán)之前使用, 一個循環(huán)之中嵌套另一個循環(huán)的開關(guān),從多重嵌套中continue或break,該文詳細(xì)介紹了標(biāo)簽的相關(guān)知識,對正在學(xué)習(xí)java基礎(chǔ)的小伙伴們還很有幫助,需要的朋友可以參考下2021-05-05淺談spring使用策略模式實現(xiàn)多種場景登錄方式
本文主要介紹了spring使用策略模式實現(xiàn)多種場景登錄方式,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12