批量新增
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>
測試結果
數量 | 耗時 |
---|---|
1000 | 1469ms |
2000 | 2534ms |
3000 | 2613ms |
4000 | 3549ms |
5000 | 4733ms |
8000 | 5761ms |
10000 | 6055ms |
2.方式二
批量新增或更新方式
注:需要給唯一主鍵添加唯一索引,update才會生效
<!-- 批量新增或更新-->
<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>
測試結果
數量 | 耗時 |
---|---|
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);
//獲得對應的Mapper
UserTestBatchDOMapper userTestBatchDOMapper = sqlSession.getMapper(UserTestBatchDOMapper.class);
try {
for (UserTestBatchDO testBatchDO : testBatchDAOList) {
userTestBatchDOMapper.insert(testBatchDO);
}
//統一提交
sqlSession.commit();
} catch (Exception e) {
//沒有提交的數據可以回滾
sqlSession.rollback();
} finally {
//關閉 sqlSession
sqlSession.close();
}
}
測試結果
數量 | 耗時 |
---|---|
1000 | 2174ms |
2000 | 3104ms |
3000 | 3801ms |
4000 | 4991ms |
5000 | 5930ms |
8000 | 8151ms |
10000 | 8252ms |
批量修改
1.方式一
批量新增或更新方式
注:需要給唯一主鍵添加唯一索引,update才會生效
<!-- 批量新增或更新-->
<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>
測試結果
注:當前表內數據行數 10000
數量 | 耗時 |
---|---|
1000 | 1505ms |
2000 | 2617ms |
3000 | 2922ms |
4000 | 3292ms |
5000 | 3443ms |
8000 | 4832ms |
10000 | 4886ms |
優點:速度快
缺點:使用特殊語法 on duplicate key update 語法 增加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);
//獲得對應的Mapper
UserTestBatchDOMapper userTestBatchDOMapper = sqlSession.getMapper(UserTestBatchDOMapper.class);
try {
for (UserTestBatchDO testBatchDO : testBatchDAOList) {
userTestBatchDOMapper.updateByUserId(testBatchDO);
}
//統一提交
sqlSession.commit();
} catch (Exception e) {
//沒有提交的數據可以回滾
sqlSession.rollback();
} finally {
//關閉 sqlSession
sqlSession.close();
}
}
測試結果
注:當前表內數據行數 10000
數量 | 耗時 |
---|---|
1000 | 3158ms |
2000 | 4324ms |
3000 | 6466ms |
4000 | 7572ms |
5000 | 9812ms |
8000 | 12846ms |
10000 | 16088ms |
優點:通過日志觀察,生成一條執行語句sql ,多行參數,統一commit
缺點:比方式一速度略慢
3.方式三
java程序循環調用單條修改語句
執行方式:一條sql ,程序循環執行
for (UserTestBatchDO userTestBatch : testBatchDAOList) {
userTestBatchDOMapper.updateByUserId(userTestBatch);
}
測試結果
注:當前表內數據行數 10000
數量 | 耗時 |
---|---|
1000 | 33907ms |
2000 | 42866ms |
3000 | 89675ms |
5000 | 104833ms |
優點:方便單條控制提交事物
缺點:耗時,耗性能、每一次循環都需要與數據庫交互一次
4.方式四
Mybatis foreach 循環
執行方式:拼接好一條sql,后執行
<!-- 接收list參數,循環着組裝sql語句,注意for循環的寫法 separator=";" 代表着每次循環完,在sql后面放一個分號 -->
<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>
測試結果
注:當前表內數據行數 10000
數量 | 耗時 |
---|---|
1000 | 2671ms |
2000 | 4170ms |
3000 | 4514ms |
4000 | 5152ms |
5000 | 6572ms |
8000 | 10209ms |
10000 | 12158ms |
優點:生成多條sql,統一執行,與數據庫交互次數少
缺點 : 生成多條拼接的update語句,update語句比較多,量大了就有可能造成sql阻塞。
5.方式五
mybatis sql 使用 case when
<!-- 批量更新第二種方法,通過 case when語句變相的進行批量更新 -->
<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>
測試結果
注:當前表內數據行數 10000
數量 | 耗時 |
---|---|
1000 | 3201ms |
2000 | 4804ms |
3000 | 6833ms |
4000 | 8554ms |
5000 | 11688ms |
8000 | 26501ms |
10000 | 34724ms |
缺點:
xml中的循環體有點多,每一個case when 都要循環一遍list集合,所以大批量拼sql的時候會比較慢。
生成多條拼接sql,sql長度過長,容易sql超長引起報錯 Packet for query is too large。
MySQL 最大允許的 packet
Mybatis批處理介紹
Mybatis內置執行器類型ExecutorType有3種
分別是
ExecutorType.SIMPLE: 不做特殊處理,為每個語句的執行創建一個新的預處理語句。
ExecutorType.REUSE: 可以復用預處理語句。
ExecutorType.BATCH:可以批量執行所有更新語句
SIMPLE與BATCH(批量)對比
默認的是simple,該模式下它為每個語句的執行創建一個新的預處理語句,單條提交sql;
而batch模式重復使用已經預處理的語句,並且批量執行所有更新語句,顯然batch性能將更優;但是批量模式無法返回自增主鍵
測試環境配置
系統:win 8.1
Mysql : 5.7
java環境:junit
注:環境不同可能會引起耗時存在差異。
總結
單次批量操作不要過大,批量新增使用方式一,批量更新方式一與方式二經過測試是最優的選擇
也可以根據安全方面綜合考慮,選擇適合的方式。