Mybatis 批量操作8種實現總結

批量新增

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();
        //清理緩存,防止溢出
        sqlSession.clearCache();
    } 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
注:環境不同可能會引起耗時存在差異。

總結

單次批量操作不要過大,批量新增使用方式一,批量更新方式一與方式二經過測試是最優的選擇
也可以根據安全方面綜合考慮,選擇適合的方式。

公衆號
更多精彩內容、編程故事、心得分享,歡迎關注公衆號
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章