Mybatis批量插入數(shù)據(jù)的兩種方式總結(jié)與對(duì)比
總體描述
軟件開(kāi)發(fā)過(guò)程中需要批量插入數(shù)據(jù)的場(chǎng)景有幾種:
- 從離線文件(excel, csv等)導(dǎo)入大批量數(shù)據(jù)到系統(tǒng)。
- 從其它系統(tǒng)定時(shí)或者人工同步大批量數(shù)據(jù)到系統(tǒng)。
- 程序自身的某些算法執(zhí)行時(shí)會(huì)生成大批量數(shù)據(jù)保存到數(shù)據(jù)庫(kù)。
上面這些場(chǎng)景都是長(zhǎng)時(shí)間的處理過(guò)程,在軟件設(shè)計(jì)時(shí)需要將其設(shè)計(jì)成帶進(jìn)度展示的異步任務(wù)(同步任務(wù)微服務(wù)有http請(qǐng)求超時(shí)的風(fēng)險(xiǎn))。異步任務(wù)可以使用消息框架。
使用批量插入技術(shù)能提升數(shù)據(jù)持久化的性能。用mybatis有兩種批量插入數(shù)據(jù)的方式可選:1. 拼接批量插入多條數(shù)據(jù)的SQL. 2. 使用Batch Insert技術(shù)。
方式一:拼接插入多條數(shù)據(jù)的SQL
mapper接口代碼
/**
* 插入數(shù)據(jù)列表
*
* @param dataList 數(shù)據(jù)列表
*/
void insertDataList(@Param("list") List<BatchData> dataList);
XML文件配置
<insert id="batchInsertData" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
INSERT INTO t_batch_data (
column1,
column2,
column3,
column4,
column5,
column6,
column7,
column8,
column9,
column10
) VALUES
<foreach item="data" collection="list" separator=",">
(
#{data.column1},
#{data.column2},
#{data.column3},
#{data.column4},
#{data.column5},
#{data.column6},
#{data.column7},
#{data.column8},
#{data.column9},
#{data.column10}
)
</foreach>
</insert>
可以看到,XML配置文件使用 foreach 對(duì)多條數(shù)據(jù)做了拼接,Value部分用逗號(hào)分隔。拼接后的SQL樣式:
INSERT INTO t_batch_data (
column1,
column2,
column3,
column4,
column5,
column6,
column7,
column8,
column9,
column10
) VALUES
(
?,
?,
?,
?,
?,
?,
?,
?,
?,
?
)
,
(
?,
?,
?,
?,
?,
?,
?,
?,
?,
?
)
,
(
?,
?,
?,
?,
?,
?,
?,
?,
?,
?
)
可以看到,拼接的SQL長(zhǎng)度跟批量插入數(shù)據(jù)的條數(shù)和單條數(shù)據(jù)的字段數(shù)相關(guān)。對(duì)于像postgres這樣限定了參數(shù)個(gè)數(shù)的數(shù)據(jù)庫(kù),需要提前對(duì)大批量數(shù)據(jù)做拆分處理。
下面的示例代碼對(duì)批量數(shù)據(jù)按200條一組做拆分,然后再入庫(kù)。
public long foreachBatchInsert(@PathVariable("amount") int amount) {
long beginTime = System.currentTimeMillis();
List<BatchData> dataList = buildDataList(amount);
// 大數(shù)據(jù)分批處理入庫(kù)
List<List<BatchData>> dataGroup = ListUtil.splitList(dataList, 200);
for (List<BatchData> group : dataGroup) {
batchInsertMapper.insertDataList(group);
}
return System.currentTimeMillis() - beginTime;
}
方式二: 使用Batch Insert技術(shù)
Mapper接口代碼
/**
* 插入單條數(shù)據(jù)
*
* @param data PO數(shù)據(jù)
*/
void insertData(@Param("data") BatchData data);XML文件配置
<insert id="insertData" useGeneratedKeys="true" keyProperty="data.id" keyColumn="id">
INSERT INTO t_batch_data (
column1,
column2,
column3,
column4,
column5,
column6,
column7,
column8,
column9,
column10
) VALUES (
#{data.column1},
#{data.column2},
#{data.column3},
#{data.column4},
#{data.column5},
#{data.column6},
#{data.column7},
#{data.column8},
#{data.column9},
#{data.column10}
)
</insert>
映射實(shí)例接口和SQL代碼與插入單個(gè)對(duì)象無(wú)異。關(guān)鍵代碼在應(yīng)用層。
應(yīng)用層代碼
public long mybatisBatchInsert(@PathVariable("amount") int amount) {
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
long beginTime = System.currentTimeMillis();
try {
BatchInsertMapper insertMapper = session.getMapper(BatchInsertMapper.class);
List<BatchData> dataList = buildDataList(amount);
for (BatchData data : dataList) {
insertMapper.insertData(data);
}
session.commit();
session.clearCache();
} catch (Exception e) {
session.rollback();
} finally {
session.close();
}
return System.currentTimeMillis() - beginTime;
}
查看打印出執(zhí)行的SQL語(yǔ)句:
INSERT INTO t_batch_data (
column1,
column2,
column3,
column4,
column5,
column6,
column7,
column8,
column9,
column10
) VALUES (
?,
?,
?,
?,
?,
?,
?,
?,
?,
?
)
攔截StatementHandler的prepare執(zhí)行方法,可以看到只執(zhí)行了一次預(yù)編譯。批量插入不會(huì)出現(xiàn)參數(shù)個(gè)數(shù)超限或者SQL語(yǔ)句超長(zhǎng)的問(wèn)題。
對(duì)比分析
性能對(duì)比
在postgres數(shù)據(jù)庫(kù)中新建了一個(gè)包含10個(gè)text類(lèi)型字段的表(t_batch_data)驗(yàn)證了一下,插入20萬(wàn)條數(shù)據(jù)時(shí)間都在15秒左右,相差不大。方案1必須做分組(參數(shù)個(gè)數(shù)超過(guò)限制);方案二本身是調(diào)用的mapper的插入單個(gè)對(duì)象的接口, 不需要做分批。
應(yīng)用場(chǎng)景分析
如表字段是固定的,字段數(shù)量也不大可以使用方案一;如表字段數(shù)量不固定(元數(shù)據(jù)驅(qū)動(dòng))推薦使用第二種方案。第二種方案在代碼執(zhí)行到session.commit()時(shí)數(shù)據(jù)才真正入庫(kù),如果在這之前使用數(shù)據(jù)庫(kù)的數(shù)據(jù)或者回填的自增ID是有問(wèn)題的。
實(shí)際產(chǎn)品開(kāi)發(fā)過(guò)程中,即使采用第二種方案也建議對(duì)大數(shù)量做分組處理,將單次操作數(shù)據(jù)庫(kù)的時(shí)間控制在2秒以?xún)?nèi)。
Demo代碼地址: https://github.com/ylforever/elon-postgres.git
總結(jié)
到此這篇關(guān)于Mybatis批量插入數(shù)據(jù)的兩種方式總結(jié)與對(duì)比的文章就介紹到這了,更多相關(guān)Mybatis批量插入數(shù)據(jù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot啟動(dòng)報(bào)錯(cuò)Whitelabel Error Page: This&nbs
當(dāng)我們使用Spring Boot框架開(kāi)發(fā)Web應(yīng)用時(shí),有時(shí)會(huì)遇到啟動(dòng)報(bào)錯(cuò)信息為"Whitelabel Error Page: This application has no explicit mapping for",種報(bào)錯(cuò)信息意味著我們的應(yīng)用缺少某個(gè)URL映射的配置,導(dǎo)致請(qǐng)求無(wú)法處理,在本篇文章中,我們將詳細(xì)討論如何解決這個(gè)問(wèn)題2024-03-03
關(guān)于yml文件字符串,List,Map的書(shū)寫(xiě)方式并使用@ConfigurationProperties注入配置類(lèi)
這篇文章主要介紹了關(guān)于yml文件字符串,List,Map的書(shū)寫(xiě)方式并使用@ConfigurationProperties注入配置類(lèi),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
Java實(shí)現(xiàn)文件上傳至服務(wù)器的方法
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)文件上傳至服務(wù)器的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01
SpringBoot bean依賴(lài)屬性配置詳細(xì)介紹
Spring容器是Spring的核心,一切SpringBean都存儲(chǔ)在Spring容器內(nèi)??梢哉f(shuō)bean是spring核心中的核心。Bean配置信息定義了Bean的實(shí)現(xiàn)及依賴(lài)關(guān)系,這篇文章主要介紹了SpringBoot bean依賴(lài)屬性配置2022-09-09
mybatis?mapper.xml中如何根據(jù)數(shù)據(jù)庫(kù)類(lèi)型選擇對(duì)應(yīng)SQL語(yǔ)句
這篇文章主要介紹了mybatis?mapper.xml中如何根據(jù)數(shù)據(jù)庫(kù)類(lèi)型選擇對(duì)應(yīng)SQL語(yǔ)句,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
SpringBoot之使用Feign實(shí)現(xiàn)微服務(wù)間的交互
這篇文章主要介紹了SpringBoot中使用Feign實(shí)現(xiàn)微服務(wù)間的交互,對(duì)微服務(wù)這方面感興趣的小伙伴可以參考閱讀本文2023-03-03
Spring?Boot集成RabbitMQ以及隊(duì)列模式操作
RabbitMQ是實(shí)現(xiàn)AMQP(高級(jí)消息隊(duì)列協(xié)議)的消息中間件的一種,下面這篇文章主要給大家介紹了關(guān)于Spring?Boot集成RabbitMQ以及隊(duì)列模式操作的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04
利用session實(shí)現(xiàn)簡(jiǎn)單購(gòu)物車(chē)功能
這篇文章主要為大家詳細(xì)介紹了利用session實(shí)現(xiàn)簡(jiǎn)單購(gòu)物車(chē)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02

