SpringBoot 集成Quartz发布、修改、暂停、删除定时任务

前言

>>>SpringBoot自带schedule

SpringBoot本身支持表达式等多种定时任务,使用起来也很方便,但是如果使用复杂的任务操作时,SpringBoot自带的稍显不足,使用SpringBoot自带的定时任务, 只需要在程序启动的时候加上@EnableScheduling

@Scheduled(cron="0/20 * * * * ?")
public void task(){
      System.out.println("task - 20秒执行一次");
}

使用十分简单,本文不过多陈述

>>>为什么使用Quartz

多任务情况下,quartz更容易管理,可以实现动态配置 ,可随时删除和修改定时任务,方便使用

1、SpringBoot集成Quartz
项目目录:
1.png

由于一些quartz集成需要导入quartz自带的一些mysql库,使用起来稍显负复杂,本文采用自己创建任务库来管理简单的定时任务
pom.xml



    
        learn
        com.lss
        1.0
    
    4.0.0
    
        2.2.1
    
    quartz
    
        
            org.springframework.boot
            spring-boot-starter
        
        
            mysql
            mysql-connector-java
        
        
            org.springframework.boot
            spring-boot-starter-data-jpa
        
        
            org.quartz-scheduler
            quartz
            ${quertz.version}
        
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

application.yml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=utf8
    username: root
    password: 12345678
    driverClassName: com.mysql.jdbc.Driver
  jpa:
    database: mysql
    show-sql: true
    hibernate:
      ddl-auto: update
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    database-platform: org.hibernate.dialect.MySQL5Dialect
  quartz:
    #相关属性配置
    properties:
      org:
        quartz:
          #          dataSource:
          #            default:
          #              driver: com.mysql.jdbc.Driver
          #              URL: jdbc:mysql://localhost:3306/jobconfig?useUnicode=true&characterEncoding=utf8
          #              user: root
          #              password: 12345678
          #              maxConnections: 5
          scheduler:
            instanceName: DefaultQuartzScheduler
            instanceId: AUTO
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            tablePrefix: qrtz_
            isClustered: false
            clusterCheckinInterval: 10000
            useProperties: true
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 10
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true

    #数据库方式
    job-store-type: JDBC
    #初始化表结构
    jdbc:
      initialize-schema: NEVER
2、项目配置
2.1、新建ScheduleQuartzJob类,实现quartz的Job接口
package com.lss.job;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

@Slf4j
public class ScheduleQuartzJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        String group = context.getJobDetail().getJobDataMap().get("group").toString();
        String name = context.getJobDetail().getJobDataMap().get("name").toString();
        log.info("执行了task...group:{}, name:{}", group, name);
        // 可在此执行定时任务的具体业务
        // ...
    }
}
2.2、新建ScheduleJobPo类,对应数据库
package com.lss.entity.po;
import lombok.Data;
import javax.persistence.*;

@Data
@Table(name = "tbl_schedule_job")
@Entity
public class ScheduleJobPo {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
   
    // 任务group名称
    @Column(name = "group_name")
    private String groupName;

    // 任务job名称
    @Column(name = "job_name")
    private String jobName;

    // cron表达式
    private String cron;

    // 0 - 代表正在执行  1 - 已删除  2 - 暂停
    @Column(name = "status")
    private Integer status;

    @Column(name = "create_time")
    private Long createTime;

    @Column(name = "modified_time")
    private Long modifiedTime;
}
2.3、新建ScheduleJobDaoRepository类,调用数据库
package com.lss.dao;
import com.lss.entity.po.ScheduleJobPo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository
public interface ScheduleJobDaoRepository extends JpaRepository, JpaSpecificationExecutor {
    public ScheduleJobPo findByIdAndStatus(Integer id, Integer status);

    public List findAllByStatus(Integer status);

    public List findByGroupNameAndJobNameAndStatus(String groupName, String jobName, Integer status);

    public List findAllByStatusInOrderByCreateTimeDesc(List statusList);
}
2.4、新建ScheduleJobService类,业务实现层
package com.lss.service;

import com.lss.dao.ScheduleJobDaoRepository;
import com.lss.entity.model.ScheduleJobModel;
import com.lss.entity.po.ScheduleJobPo;
import com.lss.job.ScheduleQuartzJob;
import com.lss.util.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Set;

@Service
@Slf4j
public class ScheduleJobService {

    // 获取工厂类
    private StdSchedulerFactory sf = new StdSchedulerFactory();

    @Autowired
    private ScheduleJobDaoRepository scheduleJobDaoRepository;

    // 项目重启后,初始化原本已经运行的定时任务
    @PostConstruct
    public void init(){
        List poList = scheduleJobDaoRepository.findAllByStatus(0);
        poList.forEach(po -> {
            startScheduleByInit(po);
        });
    }

    /**
     * 初始化时开启定时任务
     */
    private void startScheduleByInit(ScheduleJobPo po){
        try {
            Scheduler scheduler = sf.getScheduler();
            startJob(scheduler, po.getGroupName(), po.getJobName(), po.getCron());
            scheduler.start();
        }catch (Exception e){
            log.error("exception:{}", e);
        }
    }

    /**
     * 开启定时任务
     * @param model
     */
    public void startSchedule(ScheduleJobModel model) {
        if (StringUtils.isEmpty(model.getGroupName()) || StringUtils.isEmpty(model.getJobName()) || StringUtils.isEmpty(model.getCron())){
            throw new RuntimeException("参数不能为空");
        }
        List poList = scheduleJobDaoRepository.findByGroupNameAndJobNameAndStatus(model.getGroupName(), model.getJobName(), 0);
        if (!ObjectUtils.isEmpty(poList)){
            throw new RuntimeException("group和job名称已存在");
        }
        try {
            Scheduler scheduler = sf.getScheduler();
            startJob(scheduler, model.getGroupName(), model.getJobName(), model.getCron());
            scheduler.start();
            ScheduleJobPo scheduleJobPo = new ScheduleJobPo();
            scheduleJobPo.setGroupName(model.getGroupName());
            scheduleJobPo.setJobName(model.getJobName());
            scheduleJobPo.setCron(model.getCron());
            scheduleJobPo.setStatus(0);
            scheduleJobPo.setCreateTime(DateUtil.getCurrentTimeStamp());
            scheduleJobPo.setModifiedTime(DateUtil.getCurrentTimeStamp());
            scheduleJobDaoRepository.save(scheduleJobPo);
        }catch (Exception e){
            log.error("exception:{}", e);
        }

    }

    /**
     * 更新定时任务
     * @param model
     */
    public void scheduleUpdateCorn(ScheduleJobModel model) {
        if (ObjectUtils.isEmpty(model.getId()) || ObjectUtils.isEmpty(model.getCron())){
            throw new RuntimeException("定时任务不存在");
        }
        try {
            ScheduleJobPo po = scheduleJobDaoRepository.findByIdAndStatus(model.getId(), 0);
            // 获取调度对象
            Scheduler scheduler = sf.getScheduler();
            // 获取触发器
            TriggerKey triggerKey = new TriggerKey(po.getJobName(), po.getGroupName());
            CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            String oldTime = cronTrigger.getCronExpression();
            if (!oldTime.equalsIgnoreCase(model.getCron())) {
                CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(model.getCron());
                CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(po.getJobName(), po.getGroupName())
                        .withSchedule(cronScheduleBuilder).build();
                // 更新定时任务
                scheduler.rescheduleJob(triggerKey, trigger);
                po.setCron(model.getCron());
                // 更新数据库
                scheduleJobDaoRepository.save(po);
            }
        }catch (Exception e){
            log.info("exception:{}", e);
        }

    }
    /**
     * 任务 - 暂停
     */
    public void schedulePause(ScheduleJobModel model) {
        if (ObjectUtils.isEmpty(model.getId())){
            throw new RuntimeException("定时任务不存在");
        }
        ScheduleJobPo po = scheduleJobDaoRepository.findByIdAndStatus(model.getId(), 0);
        if (ObjectUtils.isEmpty(po)){
            throw new RuntimeException("定时任务不存在");
        }
        try {
            Scheduler scheduler = sf.getScheduler();
            JobKey jobKey = new JobKey(po.getJobName(), po.getGroupName());
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            if (jobDetail == null)
                return;
            scheduler.pauseJob(jobKey);
            po.setStatus(2);
            scheduleJobDaoRepository.save(po);
        }catch (Exception e){
            log.error("exception:{}", e);
        }
    }
    /**
     * 任务 - 恢复
     */

    public void scheduleResume(ScheduleJobModel model) {
        if (ObjectUtils.isEmpty(model.getId())){
            throw new RuntimeException("定时任务不存在");
        }
        ScheduleJobPo po = scheduleJobDaoRepository.findByIdAndStatus(model.getId(), 2);
        if (ObjectUtils.isEmpty(po)){
            throw new RuntimeException("定时任务不存在");
        }
        try {
            Scheduler scheduler = sf.getScheduler();
            JobKey jobKey = new JobKey(po.getJobName(), po.getGroupName());
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            if (jobDetail == null)
                return;
            scheduler.resumeJob(jobKey);
            po.setStatus(0);
            scheduleJobDaoRepository.save(po);
        }catch (Exception e){
            log.error("exception:{}", e);
        }
    }
    /**
     * 任务 - 删除一个定时任务
     */
    public void scheduleDelete(ScheduleJobModel model) {
        if (ObjectUtils.isEmpty(model.getId())){
            throw new RuntimeException("定时任务不存在");
        }
        ScheduleJobPo po = scheduleJobDaoRepository.findByIdAndStatus(model.getId(), 0);
        if (ObjectUtils.isEmpty(po)){
            throw new RuntimeException("定时任务不存在");
        }
        try {
            Scheduler scheduler = sf.getScheduler();
            JobKey jobKey = new JobKey(po.getJobName(), po.getGroupName());
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            if (jobDetail == null)
                return;
            scheduler.deleteJob(jobKey);
            po.setStatus(1);
            scheduleJobDaoRepository.save(po);
        }catch (Exception e){
            log.error("exception:{}", e);
        }
    }

    /**
     * 删除所有定时任务
     */
    public void scheduleDeleteAll() {
        try {
            Scheduler scheduler = sf.getScheduler();
            // 获取有所的组
            List jobGroupNameList = scheduler.getJobGroupNames();
            for (String jobGroupName : jobGroupNameList) {
                GroupMatcher jobKeyGroupMatcher = GroupMatcher.jobGroupEquals(jobGroupName);
                Set jobKeySet = scheduler.getJobKeys(jobKeyGroupMatcher);
                for (JobKey jobKey : jobKeySet) {
                    String jobName = jobKey.getName();
                    JobDetail jobDetail = scheduler.getJobDetail(jobKey);
                    if (jobDetail == null)
                        return;
                    scheduler.deleteJob(jobKey);
                    // 更新数据库
                    List poList = scheduleJobDaoRepository.findByGroupNameAndJobNameAndStatus(jobGroupName, jobName, 0);
                    poList.forEach(po -> {
                        po.setStatus(1);
                        scheduleJobDaoRepository.save(po);
                    });
                    log.info("group:{}, job:{}", jobGroupName, jobName);
                }
            }
        }catch (Exception e){
            log.error("exception:{}", e);
        }
    }
    // 开启任务
    private void startJob(Scheduler scheduler, String group, String name, String cron) throws SchedulerException {
        // 通过JobBuilder构建JobDetail实例,JobDetail规定只能是实现Job接口的实例
        // 在map中可传入自定义参数,在job中使用
        JobDataMap map = new JobDataMap();
        map.put("group", group);
        map.put("name", name);
        // JobDetail 是具体Job实例
        JobDetail jobDetail = JobBuilder.newJob(ScheduleQuartzJob.class).withIdentity(name, group)
                .usingJobData(map)
                .build();
        // 基于表达式构建触发器
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
        // CronTrigger表达式触发器 继承于Trigger
        // TriggerBuilder 用于构建触发器实例
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(name, group)
                .withSchedule(cronScheduleBuilder).build();
        scheduler.scheduleJob(jobDetail, cronTrigger);
    }
}
2.5、新建ScheduleJobModel类,参数调用
package com.lss.entity.model;

import lombok.Data;

@Data
public class ScheduleJobModel {

    private Integer id;

    private String groupName;

    private String jobName;

    private String cron;
}
2.6、新建TestController类,暴露restful接口,实现接口调用
package com.lss.controller;

import com.lss.entity.model.ScheduleJobModel;
import com.lss.service.ScheduleJobService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/api")
public class TestController {

    @Autowired
    private ScheduleJobService scheduleJobService;

    /**
     * 开启
     * @param model
     * @return
     */
    @PostMapping("start")
    public String startSchedule(@RequestBody ScheduleJobModel model){
        scheduleJobService.startSchedule(model);
        return "ok";
    }

    /**
     * 更新
     * @param model
     * @return
     */
    @PostMapping("update")
    public String scheduleUpdateCorn(@RequestBody ScheduleJobModel model){
        scheduleJobService.scheduleUpdateCorn(model);
        return "ok";
    }

    /**
     * 暂停
     * @param model
     * @return
     */
    @PostMapping("/pause")
    public String schedulePause(@RequestBody ScheduleJobModel model){
        scheduleJobService.schedulePause(model);
        return "ok";
    }

    /**
     * 恢复
     * @param model
     * @return
     */
    @PostMapping("/resume")
    public String scheduleResume(@RequestBody ScheduleJobModel model){
        scheduleJobService.scheduleResume(model);
        return "ok";
    }

    /**
     * 删除一个定时任务
     * @param model
     * @return
     */
    @PostMapping("/delete")
    public String scheduleDelete(@RequestBody ScheduleJobModel model){
        scheduleJobService.scheduleDelete(model);
        return "ok";
    }

    /**
     * 删除所有定时任务
     * @param model
     * @return
     */
    @PostMapping("deleteAll")
    public String scheduleDeleteAll(@RequestBody ScheduleJobModel model){
        scheduleJobService.scheduleDeleteAll();
        return "ok";
    }
}
2.7、新建DateUtil类,获取当前日期
package com.lss.util;

public class DateUtil {
    /**
     * 得到当前时间戳
     * @return
     */
    public static Long getCurrentTimeStamp() {
        long timeMillis = System.currentTimeMillis();
        return timeMillis;
    }
}
以上是该项目的所有配置
3、启动项目
3.1、调用 /api/start 接口

curl -H "Content-Type:application/json" -X POST --data '{"groupName":"group","jobName":"job","cron":"0/5 * * * * ?"}' http://localhost:8080/api/start
看控制台日志,发现每隔5秒调用一次定时服务

2.png

3.2、调用 /api/update 接口

curl -H "Content-Type:application/json" -X POST --data '{"id":1, "cron":"0/8 * * * * ?"}' http://localhost:8080/api/update
看控制台日志,发现每隔8秒调用一次定时服务

3.png

这个接口实现是先调用的数据库,然后去quartz中更新了该定时任务,也可直接传入groupName和jobName去quartz中查询出来对应的定时任务,更新后再去更新数据库

后一种方法更好,实现也很容易实现,学习的朋友们可参照接口 /api/deleteAll 中的一些业务操作实现该业务,本文不在过多陈述
3.3、调用 /api/pause 接口

curl -H "Content-Type:application/json" -X POST --data '{"id":1}' http://localhost:8080/api/pause
看控制台日志,发现定时任务已停止

3.4、调用 /api/resume 接口

curl -H "Content-Type:application/json" -X POST --data '{"id":1}' http://localhost:8080/api/resume
看控制台日志,发现定时任务已恢复

3.5、调用 /api/delete 接口

curl -H "Content-Type:application/json" -X POST --data '{"id":1}' http://localhost:8080/api/delete
发现定时任务已删除

3.6、调用 /api/deleteAll 接口

curl -H "Content-Type:application/json" -X POST --data '{}' http://localhost:8080/api/deleteAll
此时删除所有的定时任务

附录一些cron表达式,方便记忆
cron表达式
字段 允许值 允许的特殊字符
0-59 , - * /
0-59 , - * /
小时 0-23 , - * /
日期 1-31 , - * ? / L C #
月份 1-12 或者 JAN-DEC , - * /
星期 1-7 或者 SUN-SAT , - * ? / L C #
(可选)留空 1970-2099 , - * /
每个符号的意义

* 表示所有值;
? 表示未说明的值,即不关心它为何值;
- 表示一个指定的范围;
, 表示附加一个可能值
/ 符号前表示开始时间,符号后表示每次递增的值;
L("last") ("last") "L" 用在day-of-month字段意思是 "这个月最后一天";用在 day-of-week字段, 它简单意思是 "7" or "SAT"。 如果在day-of-week字段里和数字联合使用,它的意思就是 "这个月的最后一个星期几" – 例如: "6L" means "这个月的最后一个星期五". 当我们用“L”时,不指明一个列表值或者范围是很重要的,不然的话,我们会得到一些意想不到的结果。
W("weekday") 只能用在day-of-month字段。用来描叙最接近指定天的工作日(周一到周五)。例如:在day-of-month字段用“15W”指“最接近这个 月第15天的工作日”,即如果这个月第15天是周六,那么触发器将会在这个月第14天即周五触发;如果这个月第15天是周日,那么触发器将会在这个月第 16天即周一触发;如果这个月第15天是周二,那么就在触发器这天触发。注意一点:这个用法只会在当前月计算值,不会越过当前月。“W”字符仅能在 day-of-month指明一天,不能是一个范围或列表。也可以用“LW”来指定这个月的最后一个工作日。
# 只能用在day-of-week字段。用来指定这个月的第几个周几。例:在day-of-week字段用"6#3"指这个月第3个周五(6指周五,3指第3个)。如果指定的日期不存在,触发器就不会触发。
C 指和calendar联系后计算过的值。例:在day-of-month 字段用“5C”指在这个月第5天或之后包括calendar的第一天;在day-of-week字段用“1C”指在这周日或之后包括calendar的第一天。

示例
表达式 含义
*/5 * * * * ? 每隔5秒执行一次
0 */1 * * * ? 每隔1分钟执行一次
0 0 5-15 * * ? 每天5-15点整点触发
0 0/3 * * * ? 每三分钟触发一次
0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
0 0 12 ? * WED 表示每个星期三中午12点
0 0 17 ? * TUES,THUR,SAT 每周二、四、六下午五点
0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发
0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
0 0 23 L * ? 每月最后一天23点执行一次
0 15 10 L * ? 每月最后一日的上午10:15触发
0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发
0 15 10 * * ? 2005 2005年的每天上午10:15触发
0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发
0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发
30 * * * * ? 每半分钟触发任务
30 10 * * * ? 每小时的10分30秒触发任务
30 10 1 * * ? 每天1点10分30秒触发任务
30 10 1 20 * ? 每月20号1点10分30秒触发任务
30 10 1 20 10 ? * 每年10月20号1点10分30秒触发任务
30 10 1 20 10 ? 2011 2011年10月20号1点10分30秒触发任务
30 10 1 ? 10 * 2011 2011年10月每天1点10分30秒触发任务
30 10 1 ? 10 SUN 2011 2011年10月每周日1点10分30秒触发任务
15,30,45 * * * * ? 每15秒,30秒,45秒时触发任务
15-45 * * * * ? 15到45秒内,每秒都触发任务
15/5 * * * * ? 每分钟的每15秒开始触发,每隔5秒触发一次
15-30/5 * * * * ? 每分钟的15秒到30秒之间开始触发,每隔5秒触发一次
0 0/3 * * * ? 每小时的第0分0秒开始,每三分钟触发一次
0 15 10 ? * MON-FRI 星期一到星期五的10点15分0秒触发任务
0 15 10 L * ? 每个月最后一天的10点15分0秒触发任务
0 15 10 LW * ? 每个月最后一个工作日的10点15分0秒触发任务
0 15 10 ? * 5L 每个月最后一个星期四的10点15分0秒触发任务
0 15 10 ? * 5#3 每个月第三周的星期四的10点15分0秒触发任务
以上就是本文的所有内容,如有不对,欢迎指正,欢迎讨论

你可能感兴趣的:(SpringBoot 集成Quartz发布、修改、暂停、删除定时任务)