使用 MyBatis 的 BATCH 方式执行批量修改

前言

最近在优化公司的一个数据导入功能,里面涉及到比较复杂的逻辑,特别是最后一个列表更新,涉及到比较多的数据,在这里卡顿处主要为两处:
1.使用大量的查询条件去做修改删除操作
2.在一个for循环里面调用数据库做修改操作;
当数据量大的时候,服务非常的卡顿,在这里记录一下优化过程

现状

代码一: sql中有大量查询条件

Service 类

//  前方省略
        // 执行list
        List<Map<String, Object>> deleteDeformationList = new ArrayList<>();
        List<Map<String, Object>> deleteDynamicsList = new ArrayList<>();
        List<DeformationData> addDeformationList = new ArrayList<>();
        List<DynamicsData> addDynamicsList = new ArrayList<>();
        List<WarnRecord> addWarnRecordList = new ArrayList<>();
        List<MonitorPoint> updatePointList = new ArrayList<>();
        List<NewestRecord> newestRecords = new LinkedList<>();

        for(int i = 0; i < dataList.size(); i++){
            uploadData = dataList.get(i);
            dataId = dataIds.get(i);
            currView = pointViewMap.get(uploadData.getPointCode());
            if(currView == null){
                continue;
            }
            observedDate = uploadData.getObservedTime().substring(0, 10);
            deleteMap = new HashMap<>();
            deleteMap.put("pointCode", uploadData.getPointCode());
            deleteMap.put("observedTime", observedDate);
            deleteMap.put("proSiteId", currView.getProSiteId());
            deleteMap.put("monProjectId", currView.getMonProjectId());
            deleteMap.put("sourceId",sourceId);

            currWarn = warnMap.get(currView.getPointId());

            if(currView.getMonType() == MonitorConstant.PROJECTTYPE_DEFORMATION){
                // 变形监测
                deleteDeformationList.add(deleteMap);
                speedValue = getSpeedValue(uploadData, sourceId, currView.getMonProjectId());
                addDeformationList.add(createAddDeformation(sourceId, dataId, userId, currView, uploadData, speedValue));
                addWarnRecordList.addAll(createAddDeformationWarn(sourceId, dataId, userId, currView, currWarn, uploadData, speedValue,newestRecords));
            } else {
                // 力学监测
                deleteDynamicsList.add(deleteMap);
                addDynamicsList.add(createAddDynamics(sourceId, dataId, userId, currView, uploadData));
                addWarnRecordList.addAll(createAddDynamicsWarn(sourceId, dataId, userId, currView, currWarn, uploadData,newestRecords));
            }
            // 设置修改事件
            updatePoint = new MonitorPoint();
            updatePoint.setPointId(currView.getPointId());
            updatePoint.setUpdateUser(userId);
            updatePoint.setUpdateTime(LocalDateTime.now());
            updatePointList.add(updatePoint);
        }

        // 处理集合
        if(deleteDeformationList.size() > 0){
            warnRecordMapper.batchDeleteWarnRecord(deleteDeformationList);
            deformationDataMapper.batchDeleteData(deleteDeformationList);
        }
        if(deleteDynamicsList.size() > 0){
            warnRecordMapper.batchDeleteWarnRecord(deleteDynamicsList);
            dynamicsDataMapper.batchDeleteData(deleteDynamicsList);
        }

//  后方省略

mapper 中 sql 实现

    <delete id="batchDeleteWarnRecord">
        DELETE FROM  WarnRecord WHERE
        <foreach collection="lists" item="list" separator=" or " index="index">
            ( sourceId = #{list.sourceId,jdbcType=BIGINT}
            AND monProjectId = #{list.monProjectId,jdbcType=BIGINT}
            AND DATE_FORMAT(recordTime,"%Y/%m/%d") = #{list.observedTime,jdbcType=VARCHAR}
            AND pointCode = #{list.pointCode,jdbcType=VARCHAR} )
        </foreach>
    </delete>

在 where 查询条件后面,跟了太多的条件,导致查询起来非常慢

代码二: for循环中操作数据库

Service 类

    @Override
    public void updateNewestRecord(List<NewestRecord> records) {
        for (int i = 0 ; i <records.size() ; i++){
            NewestRecord record = records.get(i);
            baseMapper.updateNewestRecord(record);
        }

    }

mapper 中 sql 实现

    <update id="updateNewestRecord">
      update NewestRecord
      set
          warnStatus  = if(warnStatus=1,1,if(#{record.warnStatus} = 1,if((select count(1) from MonitorSwitchRecord msr where msr.sourceId = #{record.sourceId} and msr.pointId=#{record.pointId} and date_format(msr.closeTime,'%Y-%m-%d %H:%i') >  date_format(#{record.updateTime},'%Y-%m-%d %H:%i')) > 0,0,#{record.warnStatus}),#{record.warnStatus})),
          recordValue = #{record.recordValue},
          pointCode = #{record.pointCode},
          updateTime  = #{record.updateTime}
          <if test="record.newestWarnLevel != null and record.newestRecordTime != null">
              ,
             oldWarnLevel = IFNULL(newestWarnLevel,0),
             newestWarnLevel  = if(warnStatus=0,#{record.newestWarnLevel},if(#{record.newestWarnLevel} > IFNULL(newestWarnLevel,0),#{record.newestWarnLevel},IFNULL(newestWarnLevel,0))),
             oldRecordTime  = newestRecordTime,
             newestRecordTime  =#{record.newestRecordTime},
             warnValue = #{record.warnValue}
          </if>

      where sourceId = #{record.sourceId} and skewId = #{record.skewId} and monProjectId = #{record.monProjectId} and pointId = #{record.pointId} and warnItem = #{record.warnItem} and date_format(updateTime,'%Y-%m-%d') <![CDATA[ <=  ]]> date_format(#{record.updateTime},'%Y-%m-%d')
   </update>

之前用户导入少量数据时,可以正常使用;现在用户导入将近两千条数据,循环起来,不停的访问链接数据库,导致非常的卡顿,会导致前端超时。并且数据库服务器cpu报警

修改方案

代码一: 查出原始数据 java 中修改后在批量保存

针对代码一,先在数据库中查询满足条件的所有数据,然后在java代码中循环设置数据,再批量保存

Service 类

//            warnRecordMapper.batchDeleteWarnRecord(deleteDynamicsList);
            List<Long> recordIdList = new ArrayList<>();
            List<WarnRecord> warnRecords = warnRecordMapper.selectListByObservedDate(projectId, sourceId, observedDate);
            HashMap<String,WarnRecord> warnRecordHashMap = new HashMap<>();
            for (WarnRecord warnRecord : warnRecords) {
                warnRecordHashMap.put(warnRecord.getProjectSiteId()+ "_" +warnRecord.getMonProjectId() + "_" + warnRecord.getPointCode()  ,warnRecord);
            }
            for (Map<String, Object> stringObjectMap : deleteDynamicsList) {
                String proSiteId1 = stringObjectMap.get("proSiteId").toString();
                String monProjectId1 = stringObjectMap.get("monProjectId").toString();
                String pointCode1 = stringObjectMap.get("pointCode").toString();
                WarnRecord warnRecord1 = warnRecordHashMap.get(proSiteId1 + "_" + monProjectId1 + "_" + pointCode1);
                if (warnRecord1 != null){
                    recordIdList.add(warnRecord1.getRecordId());
                }
            }
            if (!recordIdList.isEmpty()){
                warnRecordMapper.deleteBatchIds(recordIdList);
            }

mapper 中 sql 实现

    <select id="selectListByObservedDate" resultType="com.pip.monitor.entry.WarnRecord">
        select * FROM  WarnRecord WHERE
            projectId = #{projectId,jdbcType=BIGINT}
            AND sourceId = #{sourceId,jdbcType=BIGINT}
            AND DATE_FORMAT(recordTime,"%Y/%m/%d") = #{observedDate,jdbcType=VARCHAR}
    </select>

代码二: 手动使用 MyBatis 的 BATCH 方式执行批量修改

使用 SqlSessionFactory 获取 SqlSession ,进行批量提交

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Override
    public void updateNewestRecord(List<NewestRecord> records) {
        // 使用  MyBatis 的 BATCH 方式执行批量修改  By chengqichuan On 2023/2/13 14:17
        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
        NewestRecordMapper mapper = sqlSession.getMapper(NewestRecordMapper.class);
        for (int i = 0 ; i <records.size() ; i++){
            NewestRecord record = records.get(i);
            mapper.updateNewestRecord(record);
            //每 1000 条提交一次
            if((i + 1) % 1000 == 0){
                sqlSession.flushStatements();
            }
        }
        sqlSession.flushStatements();
    }

在查找方案的时候看到网上还有一种 case函数语法 ,即把入参和条件 使用 case when 的语法组合起来,实现批量复杂数据修改,个人觉得不太清晰,

update tab_student set
    classroom = case id
        when 1 then "one"
        when 2 then "tow"
        when 3 then "three"
    end
where id in (1,2,3)

总结

在日常开发中,一定要重视这种循环调用数据库的操作,数据库连接的开启和关闭对数据库来说是巨大的消耗;还有大量数据的多个条件的复杂查询。
在数据量可控的情况下,查询有限数据在java中再来做复杂比对处理也不失为一种比较好的方案,或者某些场景下可以使用临时表等等。

相关文章

转载请注明: 转载自 浮生一程
本文链接地址 使用 MyBatis 的 BATCH 方式执行批量修改
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇