调度框架-Quartz-单机Quartz使用以及Job持久化

Quzrtz

使用关系数据库的行锁来实现执行作业的竞争, 从而保证多个进程下, 同一个任务在相同时刻, 不能重复执行

有三个组件非常重要:

  • 调度器: Scheduler
  • 触发器: Trigger(分为SimpleTrigger和CronTrigger)
  • 任务: JobDetail

Quartz 分成单机模式和集群模式。

单机

XML依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- 实现对 Quartz 的自动化配置 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
    <!-- jdbc用于后续的持久化 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.48</version>
    </dependency>
    <!-- lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
<dependencies>
<properties>
    <java.version>11</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <spring-boot.version>2.6.13</spring-boot.version>
</properties>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

定义Job

  • 继承 QuartzJobBean 抽象类, 实现 #executeInternal(JobExecutionContext context) 方法, 执行自定义的定时任务的逻辑
MyQuartzJob
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author whiteBrocade
 * @version 1.0
 * @description: MyQuartzJob
 */
@Slf4j
public class MyQuartzJob extends QuartzJobBean {

    private final AtomicInteger counts = new AtomicInteger();

    @Override
    protected void executeInternal(JobExecutionContext context) {
        log.info("[executeInternal]开始执行");
    }
}
MyQuartzJob2
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author whiteBrocade
 * @version 1.0
 * @description: MyQuartzJob2
 */
@Slf4j
public class MyQuartzJob2 extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext context) {
        log.info("[executeInternal][定时第({})次执行", counts.incrementAndGet());
    }
}

ScheduleConfiguration

import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author whiteBrocade
 * @version 1.0
 * @description: Quartz 调度器配置
 */
@Configuration
public class ScheduleConfiguration {
    @Bean
    public JobDetail myQuartzJob() {
        return JobBuilder.newJob(MyQuartzJob.class)
                .withIdentity("myQuartzJob") // 名字为 myQuartzJob
                .storeDurably() // 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时, 还没 Trigger 指向它, 所以需要设置为 true , 表示保留。
                .build();
    }

    /**
     * 创建 myQuartzJob 的 Trigger Bean 对象。其中, 我们使用 SimpleScheduleBuilder 简单的调度计划的构造器, 创建了每 5 秒执行一次, 无限重复的调度计划
     * @return Trigger Bean
     */
    @Bean
    public Trigger myQuartzJobTrigger() {
        // 简单的调度计划的构造器
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(5) // 频率。
                .repeatForever(); // 次数。
        // Trigger 构造器
        return TriggerBuilder.newTrigger()
                .forJob(myQuartzJob()) // 对应 Job 为 myQuartzJob
                .withIdentity("myQuartzJobTrigger") // 名字为 myQuartzJobTrigger
                .withSchedule(scheduleBuilder) // 对应 Schedule 为 scheduleBuilder
                .build();
    }


    @Bean
    public JobDetail myQuartzJob2() {
        return JobBuilder.newJob(MyQuartzJob2.class)
                .withIdentity("myQuartzJob2") // 名字为 myQuartzJob2
                .storeDurably() // 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时, 还没 Trigger 指向它, 所以需要设置为 true , 表示保留。
                .build();
    }

    /**
     * 创建 DemoJob02 的 Trigger Bean 对象。其中, 我们使用 CronScheduleBuilder 基于 Quartz Cron 表达式的调度计划的构造器, 创建了每第 10 秒执行一次的调度计划。
     * 推荐一个 Quartz/Cron/Crontab 表达式在线生成工具 https://www.matools.com/cron/
     * @return CronTrigger Bean
     */
    @Bean
    public CronTrigger myQuartzJobTrigger2() {
        // 基于 Quartz Cron 表达式的调度计划的构造器
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ? *");

        // Trigger 构造器
        return TriggerBuilder.newTrigger()
                .forJob(myQuartzJob2()) // 对应 Job 为 myQuartzJob
                .withIdentity("myQuartzJobTrigger2") // 名字为 myQuartzJobTrigger2
                .withSchedule(scheduleBuilder) // 对应 Schedule 为 scheduleBuilder
                .build();
    }
}

yaml配置

spring:
  # Quartz 的配置,对应 QuartzProperties 配置类
  quartz:
    job-store-type: memory # Job 存储器类型。默认为 memory 表示内存,可选 jdbc 使用数据库
    auto-startup: true # Quartz 是否自动启动
    startup-delay: 0 # 延迟 N 秒启动
    wait-for-jobs-to-complete-on-shutdown: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true
    overwrite-existing-jobs: false # 是否覆盖已有 Job 的配置
    properties: # 添加 Quartz Scheduler 附加属性,更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 文档
      org:
        quartz:
          threadPool:
            threadCount: 25 # 线程池大小。默认为 10 。
            threadPriority: 5 # 线程优先级
            class: org.quartz.simpl.SimpleThreadPool # 线程池类型
#    jdbc: # 这里暂时不说明,使用 JDBC 的 JobStore 的时候,才需要配置

持久化

持久化这块的代码和上边的代码隔离开, 建议不要混在一起

实现持久化有2种方式。

  1. 自己维护一张表,操作定时任务时同时修改这张表
  2. 使用官方的建表语句(我们用这种方式)

MySQL语句

官方表如下

-- 一个共11张表
-- 1.1. qrtz_blob_triggers :Blob 类型存储的触发器。
-- 1.2. qrtz_calendars:存放日历信息, quartz可配置一个日历来指定一个时间范围。
-- 1.3. qrtz_cron_triggers:存放cron类型的触发器。
-- 1.4. qrtz_fired_triggers:存放已触发的触发器。
-- 1.5. qrtz_job_details:存放一个jobDetail信息。
-- 1.6. qrtz_job_listeners:job监听器。
-- 1.7. qrtz_locks: 存储程序的悲观锁的信息(假如使用了悲观锁)-- 1.8. qrtz_paused_trigger_graps:存放暂停掉的触发器。
-- 1.9. qrtz_scheduler_state:调度器状态。
-- 1.10. qrtz_simple_triggers:简单触发器的信息。
-- 1.11. qrtz_trigger_listeners:触发器监听器。
-- 1.12. qrtz_triggers:触发器的基本信息。

DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;

CREATE TABLE QRTZ_JOB_DETAILS(
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(190) NOT NULL,
JOB_GROUP VARCHAR(190) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE VARCHAR(1) NOT NULL,
IS_NONCONCURRENT VARCHAR(1) NOT NULL,
IS_UPDATE_DATA VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
JOB_NAME VARCHAR(190) NOT NULL,
JOB_GROUP VARCHAR(190) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT(13) NULL,
PREV_FIRE_TIME BIGINT(13) NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT(13) NOT NULL,
END_TIME BIGINT(13) NULL,
CALENDAR_NAME VARCHAR(190) NULL,
MISFIRE_INSTR SMALLINT(2) NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
REPEAT_COUNT BIGINT(7) NOT NULL,
REPEAT_INTERVAL BIGINT(12) NOT NULL,
TIMES_TRIGGERED BIGINT(10) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_CRON_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
CRON_EXPRESSION VARCHAR(120) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SIMPROP_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(190) NOT NULL,
    TRIGGER_GROUP VARCHAR(190) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) NULL,
    INT_PROP_1 INT NULL,
    INT_PROP_2 INT NULL,
    LONG_PROP_1 BIGINT NULL,
    LONG_PROP_2 BIGINT NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 VARCHAR(1) NULL,
    BOOL_PROP_2 VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_BLOB_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_CALENDARS (
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(190) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))
ENGINE=InnoDB;

CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_FIRED_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
INSTANCE_NAME VARCHAR(190) NOT NULL,
FIRED_TIME BIGINT(13) NOT NULL,
SCHED_TIME BIGINT(13) NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(190) NULL,
JOB_GROUP VARCHAR(190) NULL,
IS_NONCONCURRENT VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SCHEDULER_STATE (
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(190) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))
ENGINE=InnoDB;

CREATE TABLE QRTZ_LOCKS (
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME))
ENGINE=InnoDB;

CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);

CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);
CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);

CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);
CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);

commit;

xml

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- 实现对 Quartz 的自动化配置 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.48</version>
    </dependency>
    <!-- druid连接池 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.23</version>
    </dependency>
</dependencies>
<properties>
    <java.version>11</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <!-- 注意一下SpringBoot的版本, 不要大于5.7,否则quartz会有数据源的问题 -->
    <spring-boot.version>2.3.2.RELEASE</spring-boot.version>
    <flink.version>1.19.0</flink.version>
</properties>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

yaml

spring:
  dataSource:
    # 注意, 这里要切换成你自己的url地址, username, password
    quartz:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://192.168.132.101:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8
      username: root
      password: 12345678
      type: com.alibaba.druid.pool.DruidDataSource
  quartz:
    # 存储模式:jdbc,即是存储到MySQL
    job-store-type: jdbc
    properties:
      org:
        quartz:
          scheduler:
            instanceName: DemoScheduler
            instanceId: AUTO
          jobStore:
            dataSource: quartzDataSource # quartz使用的数据源, 在DataSoucrConfig中有, 注意了要一致!!!
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate #跟数据库初始化脚本中配置保持一致
            tablePrefix: QRTZ_ # 表名前缀
            isClustered: false # 是否开启集群
            clusterCheckinInterval: 10000
            useProperties: false
          # 连接池配置
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 20
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true

DataSourceConfig 数据源配置

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.autoconfigure.quartz.QuartzDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

/**
 * @author whiteBrocade
 * @version 1.0
 * @description: 数据源配置
 */
@Configuration
public class DataSourceConfig {
    /**
     * quartz数据源
     * @return quartz数据源
     */
    @Bean("quartzDataSource")
    @QuartzDataSource // 这个注解表名Quartz的数据源
    @ConfigurationProperties("spring.datasource.quartz")
    public DataSource quartzDataSource () {
        return new DruidDataSource();
    }
}

实体类和枚举

JobInfo
import lombok.Data;

import java.io.Serializable;

/**
 * @author whiteBrocade
 * @version 1.0
 * @description: JobInfo
 */
@Data
public class JobInfo implements Serializable {
    private static final long serialVersionUID = 8026140551673459050L;

    /**
     * Job名称
     */
    private String jobName;

    /**
     * Job所在的分组
     */
    private String jobGroup;

    /**
     * Job类名
     */
    private String jobClassName;

    /**
     * 触发器名
     */
    private String triggerName;

    /**
     * 触发器所在的分组
     */
    private String triggerGroup;

    /**
     * cron 表达式
     */
    private String cronExpression;

    /**
     * 描述
     */
    private String description;

    /**
     * 上次执行时间
     */
    private String preFireTime;

    /**
     * 下次执行时间
     */
    private String nextFireTime;

    /**
     * Job的状态
     */
    private String state;
}
JobVO
import lombok.Data;

import java.io.Serializable;

/**
 * @author whiteBrocade
 * @version 1.0
 * @description: TODO
 */
@Data
public class JobVO implements Serializable {
    private static final long serialVersionUID = 8026140551673459050L;

    /**
     * Job名称
     */
    private String jobName;

    /**
     * Job所在的分组
     */
    private String jobGroup;

    /**
     * Job类名
     */
    private String jobClassName;

    /**
     * 触发器名
     */
    private String triggerName;

    /**
     * 触发器所在的分组
     */
    private String triggerGroup;

    /**
     * cron 表达式
     */
    private String cronExpression;

    /**
     * 描述
     */
    private String description;

    /**
     * 下次执行时间
     */
    private String nextFireTime;
}
JobStatusEnum
import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @author whiteBrocade
 * @version 1.0
 * @description: Job和触发器 是否存在的状态枚举
 */
@Getter
@AllArgsConstructor
public enum JobStatusEnum {
    /**
     * Job不存在,触发器也不存在
     */
    JOB_NOT_EXIST_AND_TRIGGERS_NOT_EXIST(-1),
    /**
     * Job存在,触发器不存在
     */
    JOB_EXIST_AND_TRIGGERS_NOT_EXIST(0),
    /**
     * Job和触发器都存在
     */
    JOB_EXIST_AND_TRIGGERS_EXIST(1)
    ;

    private final int status;
}

Quartz持久化CRUD类

QuartzService
import java.util.List;

/**
 * @author whiteBrocade
 * @version 1.0
 * @description: Quartz持久化CRUD接口
 */
public interface QuartzService {

    /**
     * 增加一个任务job
     * @param jobVO 任务job信息
     */
    void addJob(JobVO jobVO);

    /**
     * 修改一个任务job的cron
     * @param jobName job名称
     * @param jobGroup job组名
     * @param cronExpression cron时间表达式
     */
    void updateJobCron(String jobName, String jobGroup, String cronExpression);

    /**
     * 删除一个任务job
     * @param jobName job名称
     * @param jobGroup job组名
     */
    void deleteJob(String jobName, String jobGroup);

    /**
     * 暂停一个任务job
     * @param jobName job名称
     * @param jobGroup job组名
     */
    void pauseJob(String jobName, String jobGroup);

    /**
     * 恢复一个任务job
     * @param jobName job名称
     * @param jobGroup job组名
     */
    void resumeJob(String jobName, String jobGroup);

    /**
     * 立即执行一个任务job
     * @param jobName 任务名称
     * @param jobGroup 任务组名
     */
    void runAJobNow(String jobName, String jobGroup);

    /**
     * 获取所有任务job
     * @return 所有任务job
     */
    List<JobInfo> queryAllJob();

    /**
     * 获取正在运行的任务job
     * @return 正在运行的任务job
     */
    List<JobInfo> queryRunJob();
}
QuartzServiceImpl
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author whiteBrocade
 * @version 1.0
 * @description: Quartz持久化CRUD接口实现类
 */

@Slf4j
@Service
public class QuartzServiceImpl implements QuartzService {

    @Autowired
    private Scheduler scheduler;

    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @PostConstruct
    public void startScheduler() {
        try {
            scheduler.start();
        } catch (SchedulerException e) {
            log.error("启动调度器异常", e);
        }
    }

    public void addJob(JobVO jobVO) {
        JobKey jobKey = JobKey.jobKey(jobVO.getJobName(), jobVO.getJobGroup());
        int isJobExist = this.isJobAndTriggerExist(jobKey);
        if (isJobExist == JobStatusEnum.JOB_EXIST_AND_TRIGGERS_EXIST.getStatus()) {
            throw new RuntimeException("任务和相应的触发器已经存在!");
        }

        try {
            JobDetail jobDetail = null;
            // 如果任务存在但触发器不存在,从调度器中获取JobDetail。
            // 如果任务和触发器都不存在,就构建一个JobDetail, 并设置为持久化存储
            if (isJobExist == JobStatusEnum.JOB_EXIST_AND_TRIGGERS_NOT_EXIST.getStatus()) {
                jobDetail = scheduler.getJobDetail(jobKey);
            } else if (isJobExist == JobStatusEnum.JOB_NOT_EXIST_AND_TRIGGERS_NOT_EXIST.getStatus()) {
                jobDetail = JobBuilder.newJob((Class<? extends QuartzJobBean>) Class.forName(jobVO.getJobClassName()))
                        .withIdentity(jobVO.getJobName(), jobVO.getJobGroup())
                        .withDescription(jobVO.getDescription())
                        .storeDurably() // 存储到MySQL中
                        .build();
            }

            //如果jobInfo的cron表达式为空,则创建常规任务,反之创建周期任务
            if (! StringUtils.isEmpty(jobVO.getCronExpression())) {
                // 构建cron类型的触发器
                CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                        .withIdentity(jobVO.getTriggerName(), jobVO.getTriggerGroup())
                        .withSchedule(CronScheduleBuilder.cronSchedule(jobVO.getCronExpression()))
                        .build();
                // 使用调度器进行将jobDetail和触发器进行绑定
                scheduler.scheduleJob(jobDetail, cronTrigger);
            } else {
                // 构建普通的触发器
                Trigger trigger = TriggerBuilder.newTrigger()
                        .withIdentity(jobVO.getTriggerName(), jobVO.getTriggerGroup())
                        .startAt(sdf.parse(jobVO.getNextFireTime()))
                        .withSchedule(SimpleScheduleBuilder
                                .simpleSchedule()
                                .withRepeatCount(0))
                        .build();
                scheduler.scheduleJob(jobDetail, trigger);
            }
        } catch (ClassNotFoundException e) {
            log.error("任务对应的Class类不存在");
            throw new RuntimeException("任务对应的Class类不存在");
        } catch (SchedulerException e) {
            log.error("任务调度失败");
            throw new RuntimeException("任务调度失败");
        } catch (ParseException e) {
            log.error("时间转换出错");
            throw new RuntimeException("时间转换出错");
        }
    }

    /**
     * 修改 一个job的cron
     *
     * @param jobName 任务名称
     * @param jobGroup 任务组名
     * @param cronExpression cron时间表达式
     */
    @Override
    public void updateJobCron(String jobName, String jobGroup, String cronExpression) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
            Trigger trigger = scheduler.getTrigger(triggerKey);

            if (! (trigger instanceof CronTrigger)) {
                throw new RuntimeException("只有CronTrigger类型才能修改cron");
            }

            CronTrigger cronTrigger = (CronTrigger) trigger;
            if (cronTrigger.getCronExpression().equals(cronExpression)) {
                throw new RuntimeException("cron和原来一样!请修改后再更新");
            }
            log.info("新的cron表达式: {}", cronExpression);

            cronTrigger = cronTrigger.getTriggerBuilder()
                    .withIdentity(triggerKey)
                    .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
                    .build();

            // 重启触发器
            scheduler.rescheduleJob(triggerKey, cronTrigger);

            // 如果修改cron之前的job状态就是暂停的,那么修改后也为暂停的
            Trigger.TriggerState triggerState = scheduler.getTriggerState(triggerKey);
            if (Trigger.TriggerState.PAUSED.name().equals(triggerState.name())) {
                this.pauseJob(jobName, jobGroup);
            }
        } catch (SchedulerException e) {
            log.error("修改 一个job的 时间表达式异常", e);
            throw new RuntimeException("update job error!");
        }
    }

    /**
     * 删除一个job
     *
     * @param jobName job名称
     * @param jobGroup job组名
     */
    @Override
    public void deleteJob(String jobName, String jobGroup) {
        try {
            JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
            List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
            if (! triggers.isEmpty()) {
                // 如果job不是暂停状态,那么先暂停
                TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
                if (! scheduler.getTriggerState(triggerKey).equals(Trigger.TriggerState.PAUSED)) {
                    scheduler.pauseTrigger(triggerKey);
                }
                // 删除触发器
                scheduler.unscheduleJob(triggerKey);
            }
            scheduler.deleteJob(new JobKey(jobName, jobGroup));
        } catch (Exception e) {
            log.error("删除任务一个job异常", e);
            throw new RuntimeException("delete job error!");
        }
    }

    /**
     * 暂停一个job
     *
     * @param jobName job名称
     * @param jobGroup job组名
     */
    @Override
    public void pauseJob(String jobName, String jobGroup) {
        try {
            JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
            scheduler.pauseJob(jobKey);
        } catch (SchedulerException e) {
            log.error("暂停一个job异常", e);
            throw new RuntimeException("pause job error!");
        }
    }

    /**
     * 恢复一个job
     *
     * @param jobName job名称
     * @param jobGroup job组名
     */
    @Override
    public void resumeJob(String jobName, String jobGroup) {
        try {
            JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
            scheduler.resumeJob(jobKey);
        } catch (SchedulerException e) {
            log.error("恢复一个job异常", e);
            throw new RuntimeException("resume job error!");
        }
    }

    /**
     * 立即执行一个job
     *
     * @param jobName 任务名
     * @param jobGroup 任务组名
     */
    @Override
    public void runAJobNow(String jobName, String jobGroup) {
        try {
            JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
            scheduler.triggerJob(jobKey);
        } catch (SchedulerException e) {
            log.error("运行job异常", e);
            throw new RuntimeException("run a job error!");
        }
    }

    /**
     * 获取所有计划中的任务列表
     *
     * @return 所有计划中的任务列表
     */
    @Override
    public List<JobInfo> queryAllJob() {
        try {
            // 获取所有组名
            List<String> groups = scheduler.getJobGroupNames();
            if (groups.isEmpty()) {
                return Collections.emptyList();
            }
            List<JobInfo> jobInfoList = new ArrayList<>();
            for (String group : groups) {
                GroupMatcher<JobKey> matcher = GroupMatcher.groupEquals(group);
                Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);

                for (JobKey jobKey : jobKeys) {
                    JobInfo jobInfo = new JobInfo();
                    // Job名
                    jobInfo.setJobName(jobKey.getName());
                    // Job组名
                    jobInfo.setJobGroup(jobKey.getGroup());
                    // Job类名
                    JobDetail jobDetail = scheduler.getJobDetail(jobKey);
                    jobInfo.setJobClassName(jobDetail.getJobClass().getName());

                    // 根据任务名, 任务组名获取Trigger
                    Trigger trigger = scheduler.getTrigger(TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup()));
                    // Trigger相关设置
                    if (trigger != null) {
                        Trigger.TriggerState triggerState = scheduler.getTriggerState(TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup()));
                        // 触发器名
                        jobInfo.setTriggerName(jobKey.getName());
                        // 触发器组名
                        jobInfo.setTriggerGroup(jobKey.getGroup());

                        // 只有CronTrigger类型可以获取CronExpression值
                        if (trigger instanceof CronTrigger) {
                            CronTrigger cronTrigger = (CronTrigger) trigger;
                            String cronExpression = cronTrigger.getCronExpression();
                            jobInfo.setCronExpression(cronExpression);
                        }

                        // 上次运行时间
                        if (trigger.getPreviousFireTime() != null) {
                            jobInfo.setPreFireTime(sdf.format(trigger.getPreviousFireTime()));
                        }
                        // 下一次触发时间
                        if (trigger.getNextFireTime() != null) {
                            jobInfo.setNextFireTime(sdf.format(trigger.getNextFireTime()));
                        }
                        // 描述
                        jobInfo.setDescription(jobDetail.getDescription());
                        // Job运行状态
                        jobInfo.setState(triggerState.name());
                    } else {
                        // Job运行状态
                        jobInfo.setState("OVER");
                    }
                    jobInfoList.add(jobInfo);
                }
            }
            return jobInfoList;
        } catch (SchedulerException e) {
            log.error("查询所有Job异常", e);
            throw new RuntimeException("查询所有Job异常!");
        }
    }

    /**
     * 获取所有正在运行的job
     *
     * @return 所有正在运行的job
     */
    @Override
    public List<JobInfo> queryRunJob() {
        try {
            return this.queryAllJob().stream()
                    .filter(jobInfo -> jobInfo.getState().equals(Trigger.TriggerState.NORMAL.name()))
                    .collect(Collectors.toList());
        } catch (Exception e) {
            log.error("获取所有正在运行的job异常", e);
            throw new RuntimeException("获取所有正在运行的job异常!");
        }
    }

    // ------------------------------------ -----------------------------
    // ----------------------------- 辅助方法 -----------------------------
    // ------------------------------------------------------------------
    /**
     * 判断Job和触发器是否存在
     *
     * @param jobKey jobKey
     */
    private int isJobAndTriggerExist(JobKey jobKey) {
        int result = 1;
        try {
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
            if (jobDetail != null && ! triggers.isEmpty()) {
                result = JobStatusEnum.JOB_EXIST_AND_TRIGGERS_EXIST.getStatus();
            } else if (jobDetail != null && triggers.isEmpty()) {
                result = JobStatusEnum.JOB_EXIST_AND_TRIGGERS_NOT_EXIST.getStatus();
            } else {
                result = JobStatusEnum.JOB_NOT_EXIST_AND_TRIGGERS_NOT_EXIST.getStatus();
            }
            return result;
        } catch (SchedulerException e) {
            log.error("判断Job是否存在发现异常!", e);
        }
        return result;
    }
}

任务调度相关类

SchedulerJob

这个类是实际job任务的, 后续想要新增调度逻辑, 要就新建一个类继承QuartzJobBean, 注入jobTaskService, 调用你自己的实现类即可

import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

/**
 * @author whiteBrocade
 * @version 1.0
 * @description: job任务内容
 */
@Slf4j
@Component
@AllArgsConstructor
public class SchedulerJob extends QuartzJobBean {

    private final JobTaskService jobTaskService;

    @Override
    @SneakyThrows
    protected void executeInternal(JobExecutionContext jobExecutionContext) {
        try {
            log.info("开始调度: {}", LocalDateTime.now());
            jobTaskService.testScheduleJobTask();
        } catch (Exception e) {
            log.error("SchedulerJob调度失败", e);
            throw new JobExecutionException("SchedulerJob调度失败");
        }
    }
}
JobTaskService
/**
 * @author whiteBrocade
 * @version 1.0
 * @description: JobTaskService 定义自己的业务逻辑
 */
public interface JobTaskService {
    /**
     * 测试任务
     * @throws Exception
     */
    void testScheduleJobTask() throws Exception;
}
JobTaskServiceImpl
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * @author whiteBrocade
 * @version 1.0
 * @description: ScheduleJobServiceImpl 定义自己的业务逻辑
 */
@Slf4j
@Service
public class JobTaskServiceImpl implements JobTaskService {
    @Override
    public void testScheduleJobTask() {
       log.info("JobTaskServiceImpl->testScheduleJobTask()触发了");
    }
}

QuartzController

import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * @author whiteBrocade
 * @version 1.0
 * @description: Quartz Controller 用于测试接口
 */
@RestController
@AllArgsConstructor
@RequestMapping(value = "/quartz")
public class QuartzController {

    private final QuartzService quartzService;

    /**
     * 使用quartz添加job
     */
    @PostMapping(value = "/addJob")
    public void addJob(@RequestBody JobVO jobVO) {
        if (jobVO != null) {
            quartzService.addJob(jobVO);
        } else {
            throw new RuntimeException("参数错误");
        }
    }

    /**
     * 使用quartz查询所有job
     */
    @GetMapping(value = "/queryAllJob")
    public List<JobInfo> queryAllQuartzJob() {
        return quartzService.queryAllJob();
    }

    /**
     * 使用quartz查询所有运行job
     */
    @GetMapping(value = "/queryRunJob")
    public List<JobInfo> queryRunQuartzJob() {
        return quartzService.queryRunJob();
    }

    /**
     * 使用quartz删除job
     */
    @DeleteMapping(value = "/deleteJob")
    public void deleteJob(@RequestBody JobVO jobVO) {
        quartzService.deleteJob(jobVO.getJobName(), jobVO.getJobGroup());
    }

    /**
     * 暂停job
     */
    @PutMapping(value = "/pauseJob")
    public void pauseJob(@RequestBody JobVO jobVO) {
        quartzService.pauseJob(jobVO.getJobName(), jobVO.getJobGroup());
    }

    /**
     * 恢复job
     */
    @PutMapping(value = "/resumeJob")
    public void resumeJob(@RequestBody JobVO jobVO) {
        quartzService.resumeJob(jobVO.getJobName(), jobVO.getJobGroup());
    }

    /**
     * 使用quartz修改job的cron时间
     */
    @PutMapping(value = "/updateJob")
    public void updateJob(@RequestBody JobVO jobVO) {
        quartzService.updateJobCron(jobVO.getJobName(), jobVO.getJobGroup(), jobVO.getCronExpression());
    }

    /**
     * 立马执行一次job
     */
    @PostMapping(value = "/runAJobNow")
    public void runAJobNow(@RequestBody JobVO jobVO) {
        quartzService.runAJobNow(jobVO.getJobName(), jobVO.getJobGroup());
    }
}

参考资料

玩转 Spring Boot 集成篇(定时任务框架Quartz)-腾讯云开发者社区-腾讯云 (tencent.com)

Spring Boot 定时任务的技术选型对比

SpringBoot——Quartz定时框架的使用详解和总结

Springboot+quartz整合(多数据源+quartz持久化到数据库)

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值