• SpringBoot(二)集成 Quartz:2.5.4


    Quartz是一个广泛使用的开源任务调度框架,用于在Java应用程序中执行定时任务和周期性任务。它提供了强大的调度功能,允许您计划、管理和执行各种任务,从简单的任务到复杂的任务。

    以下是Quartz的一些关键特点和功能:

    • 灵活的调度器:Quartz提供了一个高度可配置的调度器,允许您根据不同的时间表执行任务,包括固定的时间、每日、每周、每月、每秒等。您可以设置任务执行的时间和频率。
    • 多任务支持:Quartz支持同时管理和执行多个任务。您可以定义多个作业和触发器,并将它们添加到调度器中。
    • 集群和分布式调度:Quartz支持集群模式,可以在多台机器上协调任务的执行。这使得Quartz非常适合大规模和分布式应用,以确保任务的高可用性和负载均衡。
    • 持久化:Quartz可以将任务和调度信息持久化到数据库中,以便在应用程序重启时不会丢失任务信息。这对于可靠性和数据保持非常重要。
    • 错过任务处理:Quartz可以配置在任务错过执行时如何处理,例如,是否立即执行、延迟执行或丢弃任务。
    • 监听器:Quartz提供了各种监听器,可以用来监视任务的执行,以及在任务执行前后执行自定义操作。
    • 多种作业类型:Quartz支持不同类型的作业,包括无状态作业(Stateless Job)和有状态作业(Stateful
      Job)。这允许您选择最适合您需求的作业类型。
    • 插件机制:Quartz具有灵活的插件机制,可以扩展其功能。您可以创建自定义插件,以满足特定需求。
    • 丰富的API:Quartz提供了丰富的Java API,使任务调度的配置和管理非常方便。

    依赖

           <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-quartz</artifactId>
                <version>2.5.4</version>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    配置

    spring.quartz.job-store-type=jdbc
    # The first boot uses ALWAYS
    spring.quartz.jdbc.initialize-schema=never
    spring.quartz.auto-startup=true
    spring.quartz.startup-delay=5s
    spring.quartz.overwrite-existing-jobs=true
    spring.quartz.properties.org.quartz.scheduler.instanceName=ClusterQuartz
    spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
    spring.quartz.properties.org.quartz.jobStore.class=org.springframework.scheduling.quartz.LocalDataSourceJobStore
    spring.quartz.properties.org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
    spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_
    spring.quartz.properties.org.quartz.jobStore.isClustered=true
    spring.quartz.properties.org.quartz.jobStore.acquireTriggersWithinLock=true
    spring.quartz.properties.org.quartz.jobStore.misfireThreshold=12000
    spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=5000
    spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
    spring.quartz.properties.org.quartz.threadPool.threadCount=1
    spring.quartz.properties.org.quartz.threadPool.threadPriority=5
    spring.quartz.properties.org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    配置解释

    spring.quartz.job-store-type=jdbc:指定使用数据库作为Quartz的作业存储。
    
    spring.quartz.jdbc.initialize-schema=never:此属性设置为"never",表示不会自动初始化Quartz数据库架构。这意味着您需要手动创建Quartz数据库表,以便Quartz正常运行。您可以使用Quartz提供的SQL脚本来创建这些表。
    
    spring.quartz.auto-startup=true:设置为true,表示Quartz在Spring Boot应用程序启动时自动启动。
    
    spring.quartz.startup-delay=5s:Quartz启动延迟时间,设置为5秒。
    
    spring.quartz.overwrite-existing-jobs=true:设置为true,表示如果任务已经存在,则覆盖现有的任务。
    
    spring.quartz.properties.org.quartz.scheduler.instanceName=ClusterQuartz:为Quartz调度器设置实例名称。
    
    spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO:Quartz调度器实例ID设置为"AUTO",这意味着它会自动分配一个唯一的实例ID。
    
    spring.quartz.properties.org.quartz.jobStore.class=org.springframework.scheduling.quartz.LocalDataSourceJobStore:指定使用org.springframework.scheduling.quartz.LocalDataSourceJobStore作为作业存储。
    
    spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate:指定使用PostgreSQL数据库的委托类。
    
    spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_:为Quartz数据库表添加前缀,以避免与其他表冲突。
    
    spring.quartz.properties.org.quartz.jobStore.isClustered=true:启用Quartz集群模式。
    
    spring.quartz.properties.org.quartz.jobStore.acquireTriggersWithinLock=true:设置为true,以确保在获取触发器时使用锁来处理并发。
    
    spring.quartz.properties.org.quartz.jobStore.misfireThreshold=12000:设置Quartz任务的超时时间,以确定任务是否错过执行。
    
    spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=5000:设置节点之间检查其状态的时间间隔。
    
    spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool:定义Quartz线程池的类型。
    
    spring.quartz.properties.org.quartz.threadPool.threadCount=1:设置线程池中线程的数量。
    
    spring.quartz.properties.org.quartz.threadPool.threadPriority=5:设置线程的优先级。
    
    spring.quartz.properties.org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true:允许线程继承初始化线程的类加载器。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    job

    
    import cn.hutool.extra.spring.SpringUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.quartz.DisallowConcurrentExecution;
    import org.quartz.Job;
    import org.quartz.JobExecutionContext;
    import org.quartz.PersistJobDataAfterExecution;
    import org.springframework.stereotype.Component;
    
    /**
     * @author Wang
     */
    @PersistJobDataAfterExecution
    @DisallowConcurrentExecution
    @Slf4j
    @Component
    public class DealerJob implements Job {
    
        @Override
        public void execute(JobExecutionContext context) {
            log.info("start Quartz job name: {}", context.getJobDetail().getKey().getName());
            DealerImportFacade dealerImportFacade = SpringUtil.getBean(DealerImportFacade.class);
    
            log.info(" start import US dealer data ");
            RequestContext.current().set(RequestContextCons.REGION, DataSourceEnum.US.toString().toLowerCase());
            try {
    //            dealerImportFacade.importUsDealerData();
                log.info(" end import US dealer data ");
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    controller

    
    import jakarta.validation.Valid;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.*;
    
    /**
     * @author Wang
     */
    @RequiredArgsConstructor
    @Slf4j
    @RestController
    @RequestMapping("/schedule/job")
    public class ScheduleJobController {
    
        final ScheduleJobService scheduleJobService;
        final QuartzHelper quartzHelper;
    
        @PostMapping
        public AjaxRespData<ScheduleJobVO> addJob(@Valid @RequestBody AddScheduleJobDTO scheduleJobDTO) {
            ScheduleJobEntity scheduleJobEntity = BeanConvertUtils.convert(scheduleJobDTO, ScheduleJobEntity.class);
            scheduleJobEntity.init();
            scheduleJobEntity.setStatus(EnumScheduleJobStatus.RUN);
            ScheduleJobData data = scheduleJobService.save(scheduleJobEntity);
            quartzHelper.scheduleJob(data);
            return AjaxRespData.success(BeanConvertUtils.convert(data, ScheduleJobVO.class));
        }
    
        @DeleteMapping("/{jobId}")
        public AjaxRespData<Void> removeJob(@PathVariable("jobId") String jobId) {
            ScheduleJobEntity scheduleJobEntity = scheduleJobService.checkExist(jobId, EnumError.E30001);
            scheduleJobService.remove(jobId);
            quartzHelper.remove(scheduleJobEntity);
            return AjaxRespData.success();
        }
    
        @PutMapping("/{jobId}")
        public AjaxRespData<ScheduleJobVO> updateJob(@PathVariable String jobId, @Valid @RequestBody AddScheduleJobDTO scheduleJobDTO) {
            ScheduleJobEntity scheduleJobEntity = BeanConvertUtils.convert(scheduleJobDTO, ScheduleJobEntity.class);
            scheduleJobEntity.setId(jobId);
            ScheduleJobData data = scheduleJobService.update(scheduleJobEntity);
            quartzHelper.scheduleJob(data);
            return AjaxRespData.success(BeanConvertUtils.convert(data, ScheduleJobVO.class));
        }
    
        @GetMapping("/{jobId}")
        public AjaxRespData<ScheduleJobVO> getJob(@PathVariable("jobId") String jobId) {
            ScheduleJobEntity scheduleJobEntity = scheduleJobService.checkExist(jobId, EnumError.E30001);
            return AjaxRespData.success(BeanConvertUtils.convert(scheduleJobEntity, ScheduleJobVO.class));
        }
    
    
        @PutMapping("/operate")
        public void operateJob(@Valid @RequestBody AddScheduleJobDTO scheduleJobDTO) {
            ScheduleJobEntity scheduleJobEntity = scheduleJobService.checkExist(scheduleJobDTO.getId(), EnumError.E30001);
            scheduleJobEntity.setStatus(scheduleJobDTO.getStatus());
            scheduleJobService.update(scheduleJobEntity);
            quartzHelper.operateJob(scheduleJobDTO.getStatus(), scheduleJobEntity);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    service

    
    import jakarta.annotation.PostConstruct;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    /**
     * @author Wang
     */
    @RequiredArgsConstructor
    @Slf4j
    @Service
    public class ScheduleJobService extends BaseService<ScheduleJobEntity, ScheduleJobData> {
    
        final ScheduleJobRepository scheduleJobRepository;
        final QuartzHelper quartzHelper;
    
        @PostConstruct
        public void init(){
            log.info("init schedule job...");
            List<ScheduleJobEntity> jobs = this.getRepository().findAll();
            for (ScheduleJobEntity job : jobs) {
                quartzHelper.scheduleJob(job);
                quartzHelper.operateJob(EnumScheduleJobStatus.PAUSE, job);
                if (job.getStatus().equals(EnumScheduleJobStatus.RUN)) {
                    quartzHelper.operateJob(EnumScheduleJobStatus.RUN, job);
                }
            }
            log.info("init schedule job completed...");
        }
    
        @Override
        public BaseRepository<ScheduleJobEntity> getRepository() {
            return scheduleJobRepository;
        }
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    helper

    
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.quartz.*;
    import org.springframework.stereotype.Component;
    
    import java.time.format.DateTimeFormatter;
    import java.util.Date;
    import java.util.Objects;
    
    /**
     * @author Wang
     */
    @RequiredArgsConstructor
    @Slf4j
    @Component
    public class QuartzHelper {
    
        final Scheduler scheduler;
    
        public void scheduleJob(ScheduleJobEntity jobInfo) {
    
            JobKey jobKey = JobKey.jobKey(jobInfo.getJobName(), jobInfo.getJobGroup());
            try {
                JobDetail jobDetail = scheduler.getJobDetail(jobKey);
                if (Objects.nonNull(jobDetail)){
                    scheduler.deleteJob(jobKey);
                }
            } catch (SchedulerException e) {
                e.printStackTrace();
            }
    
            JobDetail jobDetail = JobBuilder.newJob(getJobClass(jobInfo.getType()))
                    .withIdentity(jobKey)
                    .build();
    
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(jobInfo.getTriggerName(), jobInfo.getTriggerGroup()).startNow()
                    .withSchedule(CronScheduleBuilder.cronSchedule(jobInfo.getCronExpression()))
                    .build();
    
            try {
                scheduler.scheduleJob(jobDetail, trigger);
            } catch (SchedulerException e) {
                log.error(e.getMessage(), e);
            }
        }
    
        public void rescheduleJob(ScheduleJobEntity job) {
    
            TriggerKey triggerKey = new TriggerKey(job.getTriggerName(), job.getTriggerGroup());
            try {
                CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
    
                CronTrigger newCronTrigger = cronTrigger.getTriggerBuilder()
                        .withIdentity(triggerKey)
                        .withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression()))
                        .build();
    
                scheduler.rescheduleJob(triggerKey, newCronTrigger);
            } catch (SchedulerException e) {
                throw new RuntimeException(e);
            }
        }
    
        public void remove(ScheduleJobEntity job) {
            TriggerKey triggerKey = new TriggerKey(job.getTriggerName(), job.getTriggerGroup());
            try {
                scheduler.pauseTrigger(triggerKey);
                scheduler.unscheduleJob(triggerKey);
                scheduler.deleteJob(JobKey.jobKey(job.getTriggerName(), job.getTriggerGroup()));
            } catch (SchedulerException e) {
                throw new RuntimeException(e);
            }
        }
    
        public void unscheduleJob(ScheduleJobEntity job) {
    
            TriggerKey triggerKey = new TriggerKey(job.getTriggerName(), job.getTriggerGroup());
            try {
                scheduler.unscheduleJob(triggerKey);
            } catch (SchedulerException e) {
                throw new RuntimeException(e);
            }
        }
    
        public void operateJob(EnumScheduleJobStatus status, ScheduleJobEntity job) {
            JobKey jobKey = JobKey.jobKey(job.getJobName(), job.getJobGroup());
            try {
                switch (status) {
                    case RUN:
                        scheduler.resumeJob(jobKey);
                        break;
                    case PAUSE:
                        scheduler.pauseJob(jobKey);
                        break;
                    default:
                        throw new IllegalArgumentException();
                }
            } catch (SchedulerException e) {
                throw new RuntimeException(e);
            }
        }
    
        public String nextTime(ScheduleJobEntity job) {
            TriggerKey triggerKey = new TriggerKey(job.getTriggerName(), job.getTriggerGroup());
            try {
                Trigger trigger = scheduler.getTrigger(triggerKey);
                Date nextFireTime = trigger.getNextFireTime();
                return DateUtil.format(nextFireTime, DateTimeFormatter.ISO_DATE_TIME);
            } catch (SchedulerException e) {
                throw new RuntimeException(e);
            }
        }
    
        private Class<? extends Job> getJobClass(EnumScheduleJobType type) {
            Class<? extends Job> clazz;
            switch (type) {
                case DEALER_IMPORT:
                    clazz = DealerJob.class;
                    break;
    //            case SECONDARY_INVITING_EXPIRE:
    //                clazz = MockDeviceReportJob.class;
    //                break;
                default:
                    throw new IllegalArgumentException();
            }
            return clazz;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131

    最终效果

    实例1,8281
    在这里插入图片描述
    实例2,8282
    在这里插入图片描述

    踩坑

    定时任务执行间隔,最低设置一分钟

  • 相关阅读:
    async-validator 源码学习笔记(三):rule
    智能座舱架构与芯片- (9) 音频篇 上
    【通信】基于matlab粒子群算法5G物联网云网络优化【含Matlab源码 2160期】
    C++桌面应用开发(Qt学习)——对话框(2)常用标准对话框
    in(...) 可能会让你排查Bug到崩溃,哈哈哈
    怎么把视频压缩到最小?快把这些方法收好
    如何使用Python抓取PDF文件并自动下载到本地
    每日一练 | 华为认证真题练习Day135
    Git客户端(TortoiseGit)使用
    Qml的知识点
  • 原文地址:https://blog.csdn.net/SpringHASh/article/details/133958075