Mybatis實(shí)現(xiàn)批量操作8種小結(jié)
批量新增
1.方式一(常用)
<!-- 批量新增-->
<insert id="batchSave" parameterType="java.util.List">
INSERT INTO lp_user_test_batch
(
id,
user_id,
user_name,
user_age,
type,
create_time,
update_time
)
VALUES
<foreach collection="list" item="item" index="index" separator=",">
(
#{item.id,jdbcType=BIGINT},
#{item.userId,jdbcType=VARCHAR},
#{item.userName,jdbcType=VARCHAR},
#{item.userAge,jdbcType=INTEGER},
#{item.type,jdbcType=INTEGER},
#{item.createTime,jdbcType=TIMESTAMP},
#{item.updateTime,jdbcType=TIMESTAMP}
)
</foreach>
</insert>
測(cè)試結(jié)果
| 數(shù)量 | 耗時(shí) |
|---|---|
| 1000 | 1469ms |
| 2000 | 2534ms |
| 3000 | 2613ms |
| 4000 | 3549ms |
| 5000 | 4733ms |
| 8000 | 5761ms |
| 10000 | 6055ms |
2.方式二
批量新增或更新方式
注:需要給唯一主鍵添加唯一索引,update才會(huì)生效
<!-- 批量新增或更新-->
<insert id="batchSaveOrUpdate" parameterType="java.util.List">
INSERT INTO lp_user_test_batch
(
id,
user_id,
user_name,
user_age,
type,
create_time,
update_time
)
VALUES
<foreach collection="list" item="item" index="index" separator=",">
(
#{item.id,jdbcType=BIGINT},
#{item.userId,jdbcType=VARCHAR},
#{item.userName,jdbcType=VARCHAR},
#{item.userAge,jdbcType=INTEGER},
#{item.type,jdbcType=INTEGER},
#{item.createTime,jdbcType=TIMESTAMP},
#{item.updateTime,jdbcType=TIMESTAMP}
)
</foreach>
ON DUPLICATE KEY UPDATE
user_name = VALUES(user_name),
user_age = VALUES(user_age),
type = VALUES(type),
update_time = VALUES(update_time)
</insert>
測(cè)試結(jié)果
| 數(shù)量 | 耗時(shí) |
|---|---|
| 1000 | 1692ms |
| 2000 | 2346ms |
| 3000 | 3249ms |
| 4000 | 3443ms |
| 5000 | 3999ms |
| 8000 | 6460ms |
| 10000 | 7053ms |
3.方式三
單條sql+批量方式的SqlSession
<insert id="insert" >
INSERT INTO lp_user_test_batch
(
id,
user_id,
user_name,
user_age,
type,
create_time,
update_time
)
values
(
#{id,jdbcType=BIGINT},
#{userId,jdbcType=VARCHAR},
#{userName,jdbcType=VARCHAR},
#{userAge,jdbcType=INTEGER},
#{type,jdbcType=INTEGER},
#{createTime,jdbcType=TIMESTAMP},
#{updateTime,jdbcType=TIMESTAMP}
)
</insert>
@Resource(name = "sqlSessionFactory")
private SqlSessionFactory sqlSessionFactory;
/**
* 利用 MyBatis 批處理特性,批量提交
*/
public void batchInsert(List<UserTestBatchDO> testBatchDAOList) {
//集合非空
if (CollectionUtils.isEmpty(testBatchDAOList)) {
return;
}
//批處理方式 SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
//獲得對(duì)應(yīng)的Mapper
UserTestBatchDOMapper userTestBatchDOMapper = sqlSession.getMapper(UserTestBatchDOMapper.class);
try {
for (UserTestBatchDO testBatchDO : testBatchDAOList) {
userTestBatchDOMapper.insert(testBatchDO);
}
//統(tǒng)一提交
sqlSession.commit();
} catch (Exception e) {
//沒(méi)有提交的數(shù)據(jù)可以回滾
sqlSession.rollback();
} finally {
//關(guān)閉 sqlSession
sqlSession.close();
}
}
測(cè)試結(jié)果
| 數(shù)量 | 耗時(shí) |
|---|---|
| 1000 | 2174ms |
| 2000 | 3104ms |
| 3000 | 3801ms |
| 4000 | 4991ms |
| 5000 | 5930ms |
| 8000 | 8151ms |
| 10000 | 8252ms |
批量修改
1.方式一
批量新增或更新方式
注:需要給唯一主鍵添加唯一索引,update才會(huì)生效
<!-- 批量新增或更新-->
<insert id="batchSaveOrUpdate" parameterType="java.util.List">
INSERT INTO lp_user_test_batch
(
id,
user_id,
user_name,
user_age,
type,
create_time,
update_time
)
VALUES
<foreach collection="list" item="item" index="index" separator=",">
(
#{item.id,jdbcType=BIGINT},
#{item.userId,jdbcType=VARCHAR},
#{item.userName,jdbcType=VARCHAR},
#{item.userAge,jdbcType=INTEGER},
#{item.type,jdbcType=INTEGER},
#{item.createTime,jdbcType=TIMESTAMP},
#{item.updateTime,jdbcType=TIMESTAMP}
)
</foreach>
ON DUPLICATE KEY UPDATE
user_name = VALUES(user_name),
user_age = VALUES(user_age),
type = VALUES(type),
update_time = VALUES(update_time)
</insert>
測(cè)試結(jié)果
注:當(dāng)前表內(nèi)數(shù)據(jù)行數(shù) 10000
| 數(shù)量 | 耗時(shí) |
|---|---|
| 1000 | 1505ms |
| 2000 | 2617ms |
| 3000 | 2922ms |
| 4000 | 3292ms |
| 5000 | 3443ms |
| 8000 | 4832ms |
| 10000 | 4886ms |
優(yōu)點(diǎn):速度快
缺點(diǎn):使用特殊語(yǔ)法 on duplicate key update 語(yǔ)法 增加sql難度性
2.方式二
單條sql+批量方式的SqlSession
<update id="updateByUserId" >
UPDATE lp_user_test_batch
SET
user_name = #{userName,jdbcType=VARCHAR},
user_age = #{userAge,jdbcType=INTEGER},
type = #{type,jdbcType=INTEGER},
update_time = #{updateTime,jdbcType=TIMESTAMP}
WHERE user_id = #{userId,jdbcType=VARCHAR}
</update>
@Resource(name = "sqlSessionFactory")
private SqlSessionFactory sqlSessionFactory;
/**
* 利用 MyBatis 批處理特性,批量更新
*/
public void batchUpdate(List<UserTestBatchDO> testBatchDAOList) {
//集合非空
if (CollectionUtils.isEmpty(testBatchDAOList)) {
return;
}
//批處理方式 SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
//獲得對(duì)應(yīng)的Mapper
UserTestBatchDOMapper userTestBatchDOMapper = sqlSession.getMapper(UserTestBatchDOMapper.class);
try {
for (UserTestBatchDO testBatchDO : testBatchDAOList) {
userTestBatchDOMapper.updateByUserId(testBatchDO);
}
//統(tǒng)一提交
sqlSession.commit();
//清理緩存,防止溢出
sqlSession.clearCache();
} catch (Exception e) {
//沒(méi)有提交的數(shù)據(jù)可以回滾
sqlSession.rollback();
} finally {
//關(guān)閉 sqlSession
sqlSession.close();
}
}
測(cè)試結(jié)果
注:當(dāng)前表內(nèi)數(shù)據(jù)行數(shù) 10000
| 數(shù)量 | 耗時(shí) |
|---|---|
| 1000 | 3158ms |
| 2000 | 4324ms |
| 3000 | 6466ms |
| 4000 | 7572ms |
| 5000 | 9812ms |
| 8000 | 12846ms |
| 10000 | 16088ms |
優(yōu)點(diǎn):通過(guò)日志觀察,生成一條執(zhí)行語(yǔ)句sql ,多行參數(shù),統(tǒng)一commit
缺點(diǎn):比方式一速度略慢
3.方式三
java程序循環(huán)調(diào)用單條修改語(yǔ)句
執(zhí)行方式:一條sql ,程序循環(huán)執(zhí)行
for (UserTestBatchDO userTestBatch : testBatchDAOList) {
userTestBatchDOMapper.updateByUserId(userTestBatch);
}
測(cè)試結(jié)果
注:當(dāng)前表內(nèi)數(shù)據(jù)行數(shù) 10000
| 數(shù)量 | 耗時(shí) |
|---|---|
| 1000 | 33907ms |
| 2000 | 42866ms |
| 3000 | 89675ms |
| 5000 | ?104833ms |
優(yōu)點(diǎn):方便單條控制提交事物
缺點(diǎn):耗時(shí),耗性能、每一次循環(huán)都需要與數(shù)據(jù)庫(kù)交互一次
4.方式四
Mybatis foreach 循環(huán)
執(zhí)行方式:拼接好一條sql,后執(zhí)行
<!-- 接收l(shuí)ist參數(shù),循環(huán)著組裝sql語(yǔ)句,注意for循環(huán)的寫法
separator=";" 代表著每次循環(huán)完,在sql后面放一個(gè)分號(hào) -->
<update id="updateForeachByUserId" parameterType="java.util.List">
<foreach collection="list" item="item" separator=";">
UPDATE lp_user_test_batch
SET
user_name = #{item.userName,jdbcType=VARCHAR},
user_age = #{item.userAge,jdbcType=INTEGER},
type = #{item.type,jdbcType=INTEGER},
update_time = #{item.updateTime,jdbcType=TIMESTAMP}
WHERE user_id = #{item.userId,jdbcType=VARCHAR}
</foreach>
</update>
測(cè)試結(jié)果
注:當(dāng)前表內(nèi)數(shù)據(jù)行數(shù) 10000
| 數(shù)量 | 耗時(shí) |
|---|---|
| 1000 | 2671ms |
| 2000 | ?4170ms |
| 3000 | 4514ms |
| 4000 | 5152ms |
| 5000 | ?6572ms |
| 8000 | 10209ms |
| 10000 | 12158ms |
優(yōu)點(diǎn):生成多條sql,統(tǒng)一執(zhí)行,與數(shù)據(jù)庫(kù)交互次數(shù)少
缺點(diǎn) : 生成多條拼接的update語(yǔ)句,update語(yǔ)句比較多,量大了就有可能造成sql阻塞。
5.方式五
mybatis sql 使用 case when
<!-- 批量更新第二種方法,通過(guò) case when語(yǔ)句變相的進(jìn)行批量更新 -->
<update id="updateCaseByUserId" parameterType="java.util.List">
update lp_user_test_batch
<trim prefix="set" suffixOverrides=",">
<!-- 拼接case when 這是另一種寫法 -->
<trim prefix="user_name =case" suffix="end,">
<foreach collection="list" item="item">
<if test="item.userName!=null">
when user_id = #{item.userId,jdbcType=VARCHAR} then #{item.userName,jdbcType=VARCHAR}
</if>
</foreach>
</trim>
<trim prefix="user_age =case" suffix="end,">
<foreach collection="list" item="item">
<if test="item.userAge!=null">
when user_id = #{item.userId,jdbcType=VARCHAR} then #{item.userAge,jdbcType=INTEGER}
</if>
</foreach>
</trim>
<trim prefix="type =case" suffix="end,">
<foreach collection="list" item="item">
<if test="item.type!=null">
when user_id = #{item.userId,jdbcType=VARCHAR} then #{item.type,jdbcType=INTEGER}
</if>
</foreach>
</trim>
<trim prefix="update_time =case" suffix="end,">
<foreach collection="list" item="item">
<if test="item.type!=null">
when user_id = #{item.userId,jdbcType=VARCHAR} then #{item.updateTime,jdbcType=TIMESTAMP}
</if>
</foreach>
</trim>
</trim>
<where>
user_id in
<foreach collection="list" index="index" item="item" separator="," open="(" close=")">
#{item.userId,jdbcType=VARCHAR}
</foreach>
</where>
</update>
測(cè)試結(jié)果
注:當(dāng)前表內(nèi)數(shù)據(jù)行數(shù) 10000
| 數(shù)量 | 耗時(shí) |
|---|---|
| 1000 | 3201ms |
| 2000 | 4804ms |
| 3000 | 6833ms |
| 4000 | 8554ms |
| 5000 | 11688ms |
| 8000 | 26501ms |
| 10000 | 34724ms |
缺點(diǎn):
xml中的循環(huán)體有點(diǎn)多,每一個(gè)case when 都要循環(huán)一遍list集合,所以大批量拼sql的時(shí)候會(huì)比較慢。
生成多條拼接sql,sql長(zhǎng)度過(guò)長(zhǎng),容易sql超長(zhǎng)引起報(bào)錯(cuò) Packet for query is too large。
MySQL 最大允許的 packet

Mybatis批處理介紹
Mybatis內(nèi)置執(zhí)行器類型ExecutorType有3種
分別是
ExecutorType.SIMPLE: 不做特殊處理,為每個(gè)語(yǔ)句的執(zhí)行創(chuàng)建一個(gè)新的預(yù)處理語(yǔ)句。
ExecutorType.REUSE: 可以復(fù)用預(yù)處理語(yǔ)句。
ExecutorType.BATCH:可以批量執(zhí)行所有更新語(yǔ)句
SIMPLE與BATCH(批量)對(duì)比默認(rèn)的是simple,該模式下它為每個(gè)語(yǔ)句的執(zhí)行創(chuàng)建一個(gè)新的預(yù)處理語(yǔ)句,單條提交sql;
而batch模式重復(fù)使用已經(jīng)預(yù)處理的語(yǔ)句,并且批量執(zhí)行所有更新語(yǔ)句,顯然batch性能將更優(yōu);但是批量模式無(wú)法返回自增主鍵
測(cè)試環(huán)境配置
系統(tǒng):win 8.1
Mysql : 5.7
java環(huán)境:junit
注:環(huán)境不同可能會(huì)引起耗時(shí)存在差異。
總結(jié)
單次批量操作不要過(guò)大,批量新增使用方式一,批量更新方式一與方式二經(jīng)過(guò)測(cè)試是最優(yōu)的選擇
也可以根據(jù)安全方面綜合考慮,選擇適合的方式。
到此這篇關(guān)于Mybatis實(shí)現(xiàn)批量操作8種小結(jié)的文章就介紹到這了,更多相關(guān)Mybatis 批量操作內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中LinkedList的模擬實(shí)現(xiàn)
本文主要介紹了Java中LinkedList的模擬實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
java+vue3+el-tree實(shí)現(xiàn)樹形結(jié)構(gòu)操作代碼
基于springboot + vue3 elementPlus實(shí)現(xiàn)樹形結(jié)構(gòu)數(shù)據(jù)的添加、刪除和頁(yè)面展示,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-06-06
java8 利用reduce實(shí)現(xiàn)將列表中的多個(gè)元素的屬性求和并返回操作
這篇文章主要介紹了java8 利用reduce實(shí)現(xiàn)將列表中的多個(gè)元素的屬性求和并返回操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08
SpringBoot 自定義注解異步記錄復(fù)雜日志詳解
這篇文章主要為大家介紹了SpringBoot 自定義注解異步記錄復(fù)雜日志詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
Spring Cloud Gateway 如何修改HTTP響應(yīng)信息
這篇文章主要介紹了Spring Cloud Gateway 修改HTTP響應(yīng)信息的方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
詳解MyBatis-Plus updateById方法更新不了空字符串/null解決方法
這篇文章主要介紹了詳解MyBatis-Plus updateById方法更新不了空字符串/null解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
如何將Java與C#時(shí)間進(jìn)行互相轉(zhuǎn)換
這篇文章主要介紹了Java與C#時(shí)間互轉(zhuǎn)的方法以及JAVA日期、C#日期計(jì)算說(shuō)明,需要的朋友可以參考下2022-11-11
springboot項(xiàng)目實(shí)現(xiàn)斷點(diǎn)續(xù)傳功能
這篇文章主要介紹了springboot項(xiàng)目實(shí)現(xiàn)斷點(diǎn)續(xù)傳,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08

