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种方式。
- 自己维护一张表,操作定时任务时同时修改这张表
- 使用官方的建表语句(我们用这种方式)
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)