SpringBoot使用@Async和@Transactional注解优化接口

1、业务背景:

  项目上有一个接口需要按照前端传递的时间段范围修改6个表的数据,接口V1版本开发完成是使用的同步方式全局@Transactional注解的方式去做的,但存在一个问题就是在这六个表中,sc_xxx_rtd和sc_xxx_minute的表数据量巨大,导致接口RT时间达到了几十秒的程度,严重影响用户使用。

2、优化思路:

  使用@Async注解配合自定义异步线程池将修改6️⃣个表数据的update操作异步执行,并使用@Transactional主键保证修改数据这个操作的原子性,要不全部成功,要不全部失败;

3、代码实现:

 1 /**
 2  * 异步线程池配置
 3  *
 4  * @author zhanglei
 5  * @date 2022-02-28 9:34
 6  */
 7 @Configuration
 8 @EnableAsync
 9 public class AsyncConfig implements AsyncConfigurer {
10 
11     @Override
12     @Bean(name = "asyncTaskExecutor")
13     public Executor getAsyncExecutor() {
14         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
15         executor.setCorePoolSize(20);//核心线程数
16         executor.setMaxPoolSize(40);//最大线程数
17         executor.setQueueCapacity(1000);//队列大小
18         executor.setKeepAliveSeconds(300);//线程最大空闲时间
19         executor.setThreadNamePrefix("AsyncExecutor-"); //指定用于新创建的线程名称的前缀。
20         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//拒绝策略(一共四种,此处省略)
21         executor.initialize();
22         return executor;
23     }
24 
25     @Override
26     public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
27         System.out.println("异常处理器!!你可以自定义异常处理器~");
28         return new MyAsyncUncaughtExceptionHandler();
29     }
30 }
31 
32 @Slf4j
33 @Component
34 class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
35 
36     @Resource
37     private AsyncTaskExceptionLogMapper asyncTaskExceptionLogMapper;
38 
39     @Override
40     public void handleUncaughtException(Throwable ex, Method method, Object... params) {
41 
42         log.error("occur exception class#method: " + method.getDeclaringClass().getName() + "#" + method.getName());
43         log.error("occur exception type        : " + ex.getClass().getName());
44         log.error("occur exception exception   : " + ex.getMessage());
45         log.error("occur exception param   : " + JSONUtil.toJsonStr(Lists.newArrayList(params)));
46 47 if (ObjectUtil.isNotNull(ex) && ObjectUtil.isNotNull(method)) { 48 AsyncTaskExceptionLog asyncTaskExceptionLog = new AsyncTaskExceptionLog(); 49 asyncTaskExceptionLog.setId(IdUtil.getSnowflakeNextIdStr()); 50 asyncTaskExceptionLog.setOccurClass(method.getDeclaringClass().getName()); 51 asyncTaskExceptionLog.setOccurMethod(method.getName()); 52 asyncTaskExceptionLog.setOccurType(ex.getClass().getName()); 53 asyncTaskExceptionLog.setOccurMessgae(ex.getMessage()); 54 if (ObjectUtil.isNotNull(params)) { 55 asyncTaskExceptionLog.setOccurParam(JSONUtil.toJsonStr(Lists.newArrayList(params))); 56 } 57 asyncTaskExceptionLogMapper.insert(asyncTaskExceptionLog); 58 } 59 60 } 61 }
 1 /**
 2      * 更新sc_data_xxx和sc_data_xxx_review的数据
 3      */
 4     @Async(value = "asyncTaskExecutor")
 5     @Transactional(rollbackFor = Throwable.class)
 6     public void updateScData(String userName, DailyMaintenance dailyMaintenance, AppMaintenanceExamine appMaintenanceExamine,
 7                              String reviseStartTime, String reviseEndTime, String id, Date beforeExceptionStartTime, Date beforeExceptionEndTime) {
 8         try {
 9             log.info("进入异步方法内");
10             //获取巡检模板ID、异常因子编码列表、设备编码
11             Integer templateId = appMaintenanceExamine.getExamineTemplateId();
12             String exceptionPolCodeStr = dailyMaintenance.getExceptionPolCodeList();
13             String deviceCode = dailyMaintenance.getDeviceCode();
14 
15             //故障异常标识
16             String flag = "D";
17 
18             List<ExceptionDataOperateLog> paramList = Lists.newArrayList();
19 
20             /*
21              * 如果异常因子编码列表为空则不修改
22              * 判断运维人员上传的异常因子编码,如果是ALL则按照设备编码和时间段去查询修改全部因子review数据
23              * 否则根据解析出的因子,按照设备编码和要修改的因子polCode以及时间段去修改对应review数据
24              * 只有查询出符合条件的数据才去执行修改,否则不去执行update,避免查询不出数据而导致修改了全表数据
25              * 也给review相关数据打标识
26              */
27             updateReviewData(reviseStartTime, reviseEndTime, exceptionPolCodeStr, deviceCode, userName, flag, paramList);
28 
29             //给原始数据打标识
30             updateOriginalFlag(reviseStartTime, reviseEndTime, templateId, exceptionPolCodeStr, deviceCode, userName, flag, paramList);
31 
32             if (CollectionUtil.isNotEmpty(paramList)) {
33                 //批量插入操作日志数据到日志记录表
34                 exceptionDataOperateLogMapper.batchInsert(paramList);
35             }
36 
37             log.info("异步方法执行完毕");
38         } catch (Exception e) {
39             log.error("异步修改数据出现异常:", e);
40             //获取Spring容器对象
41             ApplicationContext applicationContext = SpringUtil.getApplicationContext();
42             RevisalErrorEvent revisalErrorEvent = new RevisalErrorEvent(this);
43             revisalErrorEvent.setId(id);
44             revisalErrorEvent.setBeforeExceptionStartTime(beforeExceptionStartTime);
45             revisalErrorEvent.setBeforeExceptionEndTime(beforeExceptionEndTime);
46             applicationContext.publishEvent(revisalErrorEvent);
47             //手动回滚事务
48             TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
49         }
50     }
/**
 * 订正异常event事件
 * @author zhangl
 * @date 2022-04-21 14:32
 */
@SuppressWarnings("ALL")
@Data
public class RevisalErrorEvent extends ApplicationEvent {

    /**
     * 日常运维表主键id
     */
    private String id;

    /**
     * 之前的异常开始时间
     */
    private Date beforeExceptionStartTime;

    /**
     * 之前的异常开始时间
     */
    private Date beforeExceptionEndTime;

    public RevisalErrorEvent(Object source) {
        super(source);
    }

}
 1 /**
 2  * @author zhangl
 3  * @date 2022-05-25 10:10
 4  */
 5 @Component
 6 @Slf4j
 7 public class RevisalErrorEventListener implements ApplicationListener<RevisalErrorEvent> {
 8 
 9     @Resource
10     private DailyMaintenanceMapper dailyMaintenanceMapper;
11 
12     /**
13      * 监听异步任务是否出现异常,出现则回滚日常运维表的数据
14      *
15      * @param event
16      */
17     @Override
18     public void onApplicationEvent(RevisalErrorEvent event) {
19         String id = event.getId();
20         Date beforeExceptionStartTime = event.getBeforeExceptionStartTime();
21         Date beforeExceptionEndTime = event.getBeforeExceptionEndTime();
22         DailyMaintenance dailyMaintenance = dailyMaintenanceMapper.selectById(id);
23         dailyMaintenance.setExceptionStartTime(beforeExceptionStartTime);
24         dailyMaintenance.setMaintenanceEndTime(beforeExceptionEndTime);
25         dailyMaintenance.setIfRevise(0);
26         dailyMaintenanceMapper.updateById(dailyMaintenance);
27     }
28 
29 }
 1     <update id="batchUpdateFlag">
 2         update sc_data_hour
 3         <set>
 4             <if test="flag != null and flag !=''">
 5                 flag = #{flag}
 6             </if>
 7         </set>
 8         <where>
 9             <if test="startDateTime != null and startDateTime != ''">
10                 and data_time &gt;= to_date(#{startDateTime},'yyyy-MM-dd hh24:mi:ss')
11             </if>
12             <if test="endDateTime != null and endDateTime != ''">
13                 and data_time &lt;= to_date(#{endDateTime},'yyyy-MM-dd hh24:mi:ss')
14             </if>
15             and flag != 'D'
16         </where>
17     </update>

 

注意事项:

  原版update的修改语句是通过where id in 的方式去修改的,但实际发现如果id的数据量特别多成千上万那种的话,sql执行的效率就非常的低,如果可以的话尽可能使用范围条件而不是in的方式

posted @ 2022-08-31 16:48  云翊宸  阅读(1051)  评论(0编辑  收藏  举报