Mybatis-Plus通過(guò)SQL注入器實(shí)現(xiàn)批量插入的實(shí)踐
前言
批量插入是實(shí)際工作中常見(jiàn)的一個(gè)功能,mysql支持一條sql語(yǔ)句插入多條數(shù)據(jù)。但是Mybatis-Plus中默認(rèn)提供的saveBatch方法并不是真正的批量插入,而是遍歷實(shí)體集合每執(zhí)行一次insert語(yǔ)句插入一條記錄。相比批量插入,性能上顯然會(huì)差很多。
今天談一下,在Mybatis-Plus中如何通過(guò)SQL注入器實(shí)現(xiàn)真正的批量插入。
一、mysql批量插入的支持
insert批量插入的語(yǔ)法支持:
INSERT INTO user (id, name, age, email) VALUES (1, 'Jone', 18, 'test1@baomidou.com'), (2, 'Jack', 20, 'test2@baomidou.com'), (3, 'Tom', 28, 'test3@baomidou.com'), (4, 'Sandy', 21, 'test4@baomidou.com'), (5, 'Billie', 24, 'test5@baomidou.com');
二、Mybatis-Plus默認(rèn)saveBatch方法解析
1、測(cè)試工程建立
測(cè)試的數(shù)據(jù)表:
CREATE TABLE `user` ( `id` bigint(20) NOT NULL COMMENT '主鍵ID', `name` varchar(30) DEFAULT NULL COMMENT '姓名', `age` int(11) DEFAULT NULL COMMENT '年齡', `email` varchar(50) DEFAULT NULL COMMENT '郵箱', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
在IDEA中配置好數(shù)據(jù)庫(kù)連接,并安裝好MybatisX-Generator插件,生成對(duì)應(yīng)表的model、mapper、service、xml文件。
生成的文件推薦保存在工程目錄下,generator目錄下。先生成文件,用戶根據(jù)自己的需要,再將文件移動(dòng)到指定目錄,這樣避免出現(xiàn)文件覆蓋。
生成實(shí)體的配置選項(xiàng),這里我勾選了Lombok和Mybatis-Plus3,生成的類(lèi)更加優(yōu)雅。
移動(dòng)生成的文件到對(duì)應(yīng)目錄:
由于都是生成的代碼,這里就不補(bǔ)充代碼了。
2、默認(rèn)批量插入saveBatch方法測(cè)試
@Test public void testBatchInsert() { System.out.println("----- batch insert method test ------"); List<User> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { User user = new User(); user.setName("test"); user.setAge(13); user.setEmail("101@qq.com"); list.add(user); } userService.saveBatch(list); }
執(zhí)行日志:
顯然,這里每次執(zhí)行insert操作,都只插入了一條數(shù)據(jù)。
3、saveBatch方法實(shí)現(xiàn)分析
//批量保存的方法,做了分批請(qǐng)求處理,默認(rèn)一次處理1000條數(shù)據(jù) default boolean saveBatch(Collection<T> entityList) { return this.saveBatch(entityList, 1000); } //用戶也可以自己指定每批處理的請(qǐng)求數(shù)量 boolean saveBatch(Collection<T> entityList, int batchSize);
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 idxLimit = Math.min(batchSize, size); int i = 1; for(Iterator var7 = list.iterator(); var7.hasNext(); ++i) { E element = var7.next(); consumer.accept(sqlSession, element); //每次達(dá)到批次數(shù),sqlSession就刷新一次,進(jìn)行數(shù)據(jù)庫(kù)請(qǐng)求,生成Id if (i == idxLimit) { sqlSession.flushStatements(); idxLimit = Math.min(idxLimit + batchSize, size); } } }); }
我們將批次數(shù)設(shè)置為3,用來(lái)測(cè)試executeBatch的處理機(jī)制。
@Test public void testBatchInsert() { System.out.println("----- batch insert method test ------"); List<User> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { User user = new User(); user.setName("test"); user.setAge(13); user.setEmail("101@qq.com"); list.add(user); } //批次數(shù)設(shè)為3,用來(lái)測(cè)試 userService.saveBatch(list,3); }
執(zhí)行結(jié)果,首批提交的請(qǐng)求,已經(jīng)生成了id,還沒(méi)有提交的id為null。
(這里的提交是sql請(qǐng)求,而不是說(shuō)的事物提交)
小結(jié):
Mybatis-Plus中默認(rèn)的批量保存方法saveBatch,底層是通過(guò)sqlSession.flushStatements()
將一個(gè)個(gè)單條插入的insert語(yǔ)句分批次進(jìn)行提交。
相比遍歷集合去調(diào)用userMapper.insert(entity),執(zhí)行一次提交一次,saveBatch批量保存有一定的性能提升,但從sql層面上來(lái)說(shuō),并不算是真正的批量插入。
補(bǔ)充:
遍歷集合單次提交的批量插入。
@Test public void forEachInsert() { System.out.println("forEachInsert 插入開(kāi)始========"); long start = System.currentTimeMillis(); for (int i = 0; i < list.size(); i++) { userMapper.insert(list.get(i)); } System.out.println("foreach 插入耗時(shí):"+(System.currentTimeMillis()-start)); }
三、Mybatis-plus中SQL注入器介紹
SQL注入器官方文檔:https://baomidou.com/pages/42ea4a/
1.sqlInjector介紹
SQL注入器sqlInjector 用于注入 ISqlInjector
接口的子類(lèi),實(shí)現(xiàn)自定義方法注入。
參考默認(rèn)注入器 DefaultSqlInjector
。
Mybatis-plus默認(rèn)可以注入的方法如下,大家也可以參考其實(shí)現(xiàn)自己擴(kuò)展:
默認(rèn)注入器DefaultSqlInjector的內(nèi)容:
public class DefaultSqlInjector extends AbstractSqlInjector { public DefaultSqlInjector() { } public List<AbstractMethod> getMethodList(Class<?> mapperClass) { //注入通用的dao層接口的操作方法 return (List)Stream.of(new Insert(), new Delete(), new DeleteByMap(), new DeleteById(), new DeleteBatchByIds(), new Update(), new UpdateById(), new SelectById(), new SelectBatchByIds(), new SelectByMap(), new SelectOne(), new SelectCount(), new SelectMaps(), new SelectMapsPage(), new SelectObjs(), new SelectList(), new SelectPage()).collect(Collectors.toList()); } }
2.擴(kuò)展中提供的4個(gè)可注入方法實(shí)現(xiàn)
目前在mybatis-plus
的擴(kuò)展插件中com.baomidou.mybatisplus.extension
,給我們額外提供了4個(gè)注入方法。
AlwaysUpdateSomeColumnById
根據(jù)Id更新每一個(gè)字段,全量更新不忽略null字段,解決mybatis-plus中updateById默認(rèn)會(huì)自動(dòng)忽略實(shí)體中null值字段不去更新的問(wèn)題。InsertBatchSomeColumn
真實(shí)批量插入,通過(guò)單SQL的insert語(yǔ)句實(shí)現(xiàn)批量插入DeleteByIdWithFill
帶自動(dòng)填充的邏輯刪除,比如自動(dòng)填充更新時(shí)間、操作人Upsert
更新or插入,根據(jù)唯一約束判斷是執(zhí)行更新還是刪除,相當(dāng)于提供insert on duplicate key update支持
insert into t_name (uid, app_id,createTime,modifyTime) values(111, 1000000,'2017-03-07 10:19:12','2017-03-07 10:19:12') on duplicate key update uid=111, app_id=1000000, createTime='2017-03-07 10:19:12',modifyTime='2017-05-07 10:19:12'
mysql在存在主鍵沖突或者唯一鍵沖突的情況下,根據(jù)插入策略不同,一般有以下三種避免方法。
- insert ignore
- replace into
- insert on duplicate key update
這里不展開(kāi)介紹,大家可以自行查看:http://chabaoo.cn/article/194579.htm
四、通過(guò)SQL注入器實(shí)現(xiàn)真正的批量插入
通過(guò)SQL注入器sqlInjector 增加批量插入方法InsertBatchSomeColumn的過(guò)程如下:
1.繼承DefaultSqlInjector擴(kuò)展自定義的SQL注入器
代碼如下:
/** * 自定義Sql注入 */ public class MySqlInjector extends DefaultSqlInjector { @Override public List<AbstractMethod> getMethodList(Class<?> mapperClass) { List<AbstractMethod> methodList = super.getMethodList(mapperClass); //更新時(shí)自動(dòng)填充的字段,不用插入值 methodList.add(new InsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.UPDATE)); return methodList; } }
2.將自定義的SQL注入器注入到Mybatis容器中
代碼如下:
@Configuration public class MybatisPlusConfig { @Bean public MySqlInjector sqlInjector() { return new MySqlInjector(); } }
3.繼承 BaseMapper 添加自定義方法
public interface CommonMapper<T> extends BaseMapper<T> { /** * 全量插入,等價(jià)于insert * @param entityList * @return */ int insertBatchSomeColumn(List<T> entityList); }
4.Mapper層接口繼承新的CommonMapper
public interface UserMapper extends CommonMapper<User> { }
5.單元測(cè)試
@Test public void testBatchInsert() { System.out.println("----- batch insert method test ------"); List<User> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { User user = new User(); user.setName("test"); user.setAge(13); user.setEmail("101@qq.com"); list.add(user); } userMapper.insertBatchSomeColumn(list); }
執(zhí)行結(jié)果:
可以看到已經(jīng)實(shí)現(xiàn)單條insert語(yǔ)句支持?jǐn)?shù)據(jù)的批量插入。
注意??:
默認(rèn)的insertBatchSomeColumn實(shí)現(xiàn)中,并沒(méi)有類(lèi)似saveBatch中的分配提交處理,
這就存在一個(gè)問(wèn)題,如果出現(xiàn)一個(gè)非常大的集合,就會(huì)導(dǎo)致最后組裝提交的insert語(yǔ)句的長(zhǎng)度超過(guò)mysql的限制。
6.insertBatchSomeColumn添加分批處理機(jī)制
@Service @Slf4j public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { @Resource private UserMapper userMapper; /** * 采用insertBatchSomeColumn重寫(xiě)saveBatch方法,保留分批處理機(jī)制 * @param entityList * @param batchSize * @return */ @Override @Transactional(rollbackFor = {Exception.class}) public boolean saveBatch(Collection<User> entityList, int batchSize) { try { int size = entityList.size(); int idxLimit = Math.min(batchSize, size); int i = 1; //保存單批提交的數(shù)據(jù)集合 List<User> oneBatchList = new ArrayList<>(); for(Iterator<User> var7 = entityList.iterator(); var7.hasNext(); ++i) { User element = var7.next(); oneBatchList.add(element); if (i == idxLimit) { userMapper.insertBatchSomeColumn(oneBatchList); //每次提交后需要清空集合數(shù)據(jù) oneBatchList.clear(); idxLimit = Math.min(idxLimit + batchSize, size); } } }catch (Exception e){ log.error("saveBatch fail",e); return false; } return true; }
更好的實(shí)現(xiàn)是繼承ServiceImpl實(shí)現(xiàn)類(lèi),自己擴(kuò)展通用的服務(wù)實(shí)現(xiàn)類(lèi),在其中重寫(xiě)通用的saveBatch方法,這樣就不用在每一個(gè)服務(wù)類(lèi)中都重寫(xiě)一遍saveBatch方法。
單元測(cè)試:
@Test public void testBatchInsert() { System.out.println("----- batch insert method test ------"); List<User> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { User user = new User(); user.setName("test"); user.setAge(13); user.setEmail("101@qq.com"); list.add(user); } //批次數(shù)設(shè)為3,用來(lái)測(cè)試 userService.saveBatch(list,3); }
執(zhí)行結(jié)果:
分4次采用insert批量新增,符合我們的結(jié)果預(yù)期。
總結(jié)
本文主要介紹了Mybatis-Plus中如何通過(guò)SQL注入器實(shí)現(xiàn)真正的批量插入。主要掌握如下內(nèi)容:
1、了解Mybatis-Plus中SQL注入器有什么作用,如何去進(jìn)行擴(kuò)展。
2、默認(rèn)的4個(gè)擴(kuò)展方法各自的作用。
3、默認(rèn)的saveBatch批量新增和通過(guò)insertBatchSomeColumn實(shí)現(xiàn)的批量新增的底層實(shí)現(xiàn)原理的區(qū)別,為什么insertBatchSomeColumn性能更好以及存在哪些弊端。
4、為insertBatchSomeColumn添加分批處理機(jī)制,避免批量插入的insert語(yǔ)句過(guò)長(zhǎng)問(wèn)題。
到此這篇關(guān)于Mybatis-Plus通過(guò)SQL注入器實(shí)現(xiàn)批量插入的實(shí)踐的文章就介紹到這了,更多相關(guān)Mybatis-Plus SQL注入器批量插入內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java在PowerPoint幻燈片中創(chuàng)建散點(diǎn)圖的方法
散點(diǎn)圖是通過(guò)兩組數(shù)據(jù)構(gòu)成多個(gè)坐標(biāo)點(diǎn),考察坐標(biāo)點(diǎn)的分布,判斷兩變量之間是否存在某種關(guān)聯(lián)或總結(jié)坐標(biāo)點(diǎn)的分布模式,這篇文章主要介紹了Java如何在PowerPoint幻燈片中創(chuàng)建散點(diǎn)圖,需要的朋友可以參考下2023-04-04基于java實(shí)現(xiàn)websocket協(xié)議過(guò)程詳解
這篇文章主要介紹了基于java實(shí)現(xiàn)websocket協(xié)議過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09IDEA創(chuàng)建方法時(shí)如何快速添加注釋
這篇文章主要介紹了IDEA創(chuàng)建方法時(shí)如何快速添加注釋問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02Java中IO流 RandomAccessFile類(lèi)實(shí)例詳解
這篇文章主要介紹了Java中IO流 RandomAccessFile類(lèi)實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-05-05教你利用springboot集成swagger并生成接口文檔
有很多小伙伴不會(huì)利用springboot集成swagger并生成接口文檔,今天特地整理了這篇文章,文中有非常詳細(xì)的代碼圖文介紹及代碼示例,對(duì)不會(huì)這個(gè)方法的小伙伴們很有幫助,需要的朋友可以參考下2021-05-05詳細(xì)聊聊Spring MVC重定向與轉(zhuǎn)發(fā)
大家應(yīng)該都知道請(qǐng)求重定向和請(qǐng)求轉(zhuǎn)發(fā)都是web開(kāi)發(fā)中資源跳轉(zhuǎn)的方式,這篇文章主要給大家介紹了關(guān)于Spring MVC重定向與轉(zhuǎn)發(fā)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09Java實(shí)現(xiàn)獲取cpu、內(nèi)存、硬盤(pán)、網(wǎng)絡(luò)等信息的方法示例
這篇文章主要介紹了Java實(shí)現(xiàn)獲取cpu、內(nèi)存、硬盤(pán)、網(wǎng)絡(luò)等信息的方法,涉及java使用第三方j(luò)ar包針對(duì)本機(jī)硬件的cpu、內(nèi)存、硬盤(pán)、網(wǎng)絡(luò)信息等的讀取相關(guān)操作技巧,需要的朋友可以參考下2018-06-06