1、Quartz持久化功能概述
1、实现使用quartz提供的默认11张持久化表存储quartz相关信息。
2、实现定时任务的编辑、启动、关闭、删除。
3、实现自定义持久化表存储quartz定时任务信息。
4、本案例使用springboot整合mybatis框架和MySQL数据库实现持久化
5、提供源码下载
2、认识quartz持久化
可以将调度信息存储到数据库中,进行持久化,当程序中断,再次启动的时候。任然会保留中断之前的数据,继续执行。
1、Quartz中通过JobStore来存储任务和触发器信息,Quartz默认使用RAMJobstore将任务和触发器的信息存储在内存中,但是当服务器宕机内存中的信息会丢失。
2、Quartz中通过JDBCJobStore将任务和触发器等信息保存在数据库中,实现持久化。
3、JDBCJobStoreSupport中包含两个子类:
JobStoreTX:表示自己管理事务,存储在数据库中,当程序中断,调度信息不会丢失,
支持事务,支持集群,再次启动时,会恢复因程序关闭,重启而错过的任务。
JobStoreCMT:表示使用容器管理事务
3、Quartz+MySQL持久化默认依赖的表信息
针对不同的数据库,表就放在不同的sql文件中。
如果你使用的是mysql数据库表信息就在tables_mysql.sql文件中。
重点:这些表不会自动执行,需要拷贝出来手动创建在你的数据库中。
mysql表信息如下
QRTZ _JOB_DETAILS 存储每一个已配置的Job的详细信息
QRTZ _TRIGGERS 存储已配置的Trigger的信息
QRTZ_BLOB_TRIGGERS Trigger作为Blob类型存储
QRTZ _SIMPLE_TRIGGERS 存储SimpleTrigger的信息,包括重复次数、间隔、以及已触的次数
QRTZ _CRON_TRIGGERS 存储CronTrigger,包括Cron表达式和时区信息
QRTZ _SIMPROP_TRIGGERS存储CalendarIntervalTrigger和DailyTimeIntervalTrigger两种触发器
QRTZ _CALENDARS 存储Quartz的Calendar信息
QRTZ _PAUSED_TRIGGER_GRPS 存储已暂停的Trigger组的信息
QRTZ _FIRED_TRIGGERS 存储与已触发的Trigger相关的状态信息,以及相关Job的执行信息
QRTZ _SCHEDULER_STATE 存储少量的有关Scheduler的状态信息,和别的Scheduler实例
QRTZ _LOCKS 存储程序的悲观锁的信息
创表信息如下:
#
# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
#
# PLEASE consider using mysql with innodb tables to avoid locking issues
#
# In your Quartz properties file, you'll need to set
# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#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(200) NOT NULL,JOB_GROUP VARCHAR(200) 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)
);CREATE TABLE QRTZ_TRIGGERS(SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_NAME VARCHAR(200) NOT NULL,TRIGGER_GROUP VARCHAR(200) NOT NULL,JOB_NAME VARCHAR(200) NOT NULL,JOB_GROUP VARCHAR(200) 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(200) 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)
);CREATE TABLE QRTZ_SIMPLE_TRIGGERS(SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_NAME VARCHAR(200) NOT NULL,TRIGGER_GROUP VARCHAR(200) 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)
);CREATE TABLE QRTZ_CRON_TRIGGERS(SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_NAME VARCHAR(200) NOT NULL,TRIGGER_GROUP VARCHAR(200) NOT NULL,CRON_EXPRESSION VARCHAR(200) 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)
);CREATE TABLE QRTZ_SIMPROP_TRIGGERS(SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_NAME VARCHAR(200) NOT NULL,TRIGGER_GROUP VARCHAR(200) 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)
);CREATE TABLE QRTZ_BLOB_TRIGGERS(SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_NAME VARCHAR(200) NOT NULL,TRIGGER_GROUP VARCHAR(200) NOT NULL,BLOB_DATA BLOB 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)
);CREATE TABLE QRTZ_CALENDARS(SCHED_NAME VARCHAR(120) NOT NULL,CALENDAR_NAME VARCHAR(200) NOT NULL,CALENDAR BLOB NOT NULL,PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS(SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_GROUP VARCHAR(200) NOT NULL,PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);CREATE TABLE QRTZ_FIRED_TRIGGERS(SCHED_NAME VARCHAR(120) NOT NULL,ENTRY_ID VARCHAR(95) NOT NULL,TRIGGER_NAME VARCHAR(200) NOT NULL,TRIGGER_GROUP VARCHAR(200) NOT NULL,INSTANCE_NAME VARCHAR(200) 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(200) NULL,JOB_GROUP VARCHAR(200) NULL,IS_NONCONCURRENT VARCHAR(1) NULL,REQUESTS_RECOVERY VARCHAR(1) NULL,PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);CREATE TABLE QRTZ_SCHEDULER_STATE(SCHED_NAME VARCHAR(120) NOT NULL,INSTANCE_NAME VARCHAR(200) NOT NULL,LAST_CHECKIN_TIME BIGINT(13) NOT NULL,CHECKIN_INTERVAL BIGINT(13) NOT NULL,PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);CREATE TABLE QRTZ_LOCKS(SCHED_NAME VARCHAR(120) NOT NULL,LOCK_NAME VARCHAR(40) NOT NULL,PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);commit;
4、在MySQL数据库中创建表
5、quartz默认配置文档
配置文件位置:
配置参考文档:
https://www.w3cschool.cn/quartz_doc/quartz_doc-i7oc2d9l.html
6、创建springboot工程配置quartz持久化
在正式开始之前我们需要明确一点,quartz自带的表数据的添加功能是quartz源码中自带的,我们只需要正确的配置数据源即可自动的添加数据。
6.1、创建工程引入相关包信息
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.7</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>quartzpersistencedemo3</artifactId><version>0.0.1-SNAPSHOT</version><name>quartzpersistencedemo3</name><description>quartzpersistencedemo3</description><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.49</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><image><builder>paketobuildpacks/builder-jammy-base:latest</builder></image><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build>
</project>
6.2、配置连接数据相关参数
最重要的配置就是连接数据库及job-store-type: jdbc,其他的配置如果不需要都可以不写。
最重要的配置就是连接数据库及job-store-type: jdbc,其他的配置如果不需要都可以不写。
spring:datasource:url: jdbc:mysql://localhost:3306/quartz?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=falsedriver-class-name: com.mysql.jdbc.Driverusername: rootpassword: 123456quartz:# 任务存储类型job-store-type: jdbc# 关闭时等待任务完成wait-for-jobs-to-complete-on-shutdown: false# 是否覆盖已有的任务overwrite-existing-jobs: true# 是否自动启动计划程序auto-startup: true# 延迟启动startup-delay: 0sjdbc:# 数据库架构初始化模式(never:从不进行初始化;always:每次都清空数据库进行初始化;embedded:只初始化内存数据库(默认值))# 注意:第一次启动后,需要将always改为never,否则后续每次启动都会重新初始化quartz数据库initialize-schema: never# 用于初始化数据库架构的SQL文件的路径# schema: classpath:sql/tables_mysql_innodb.sql# 相关属性配置properties:org:quartz:scheduler:# 调度器实例名称instanceName: QuartzScheduler# 分布式节点ID自动生成instanceId: AUTOjobStore:class: org.springframework.scheduling.quartz.LocalDataSourceJobStoredriverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate# 表前缀tablePrefix: QRTZ_# 是否开启集群isClustered: true# 数据源别名(自定义)dataSource: quartz# 分布式节点有效性检查时间间隔(毫秒)clusterCheckinInterval: 10000useProperties: false# 线程池配置threadPool:class: org.quartz.simpl.SimpleThreadPoolthreadCount: 10threadPriority: 5threadsInheritContextClassLoaderOfInitializingThread: true
6.3、创建quartz配置类
在配置类中配置JobDetail和trigger监听器
这个配置类只是添加任务的快捷方式,这中方式在下面的添加任务中会被替代。
@Configuration
public class QuartzJobConfig {@Beanpublic JobDetail jobDetail(){JobDetail detail= JobBuilder.newJob(MyQuartzJob.class).withIdentity("job33","group33").storeDurably()//设置Job持久化//设置job数据.usingJobData("username","xiaochun2").usingJobData("useraddr","安徽合肥2").build();return detail;}//创建触发器,触发实例@Beanpublic Trigger trigger(){//每隔5秒执行一次CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");Trigger trigger= TriggerBuilder.newTrigger().forJob(jobDetail()).withIdentity("trigger33","group33").withSchedule(cronScheduleBuilder).startNow().build();return trigger;}
}
6.4、创建job,配置任务具体内容
@Slf4j
public class MyQuartzJob extends QuartzJobBean {@Overrideprotected void executeInternal(JobExecutionContext context) throws JobExecutionException {//执行的具体内容log.info("=========quartzpersistencedemo3--quartz具体内容============");System.out.println(context.getJobInstance());System.out.println(context.getJobDetail().getJobDataMap().get("username"));}
}
6.5、启动任务并自动向数据库添加数据
从图中可以看出,任务已经顺利的启动
6.6、查看数据库数据情况
【qrtz_cron_triggers表数据】
【qrtz_job_details表数据】
【qrtz_triggers表数据】
【qrtz_scheduler_state表数据】
6.7、暂停任务
说明1:通过scheduler.pauseJob暂停任务。
说明2:scheduler.pauseJob需要的两个参数是qrtz_job_details表中的JOB_NAME和JOB_GROUP字段的值。
说明3:本案例中只涉及到任务本身的暂停,考虑到业务的并发和分布式的情况,在暂停前可以判断一下任务是否存在,如果还创建了自己的业务表,应该在任务暂停后修改自己的表状态。个人业务表的所有操作按照常规操作即可。
@Controller
public class QuartzController {@ResourceScheduler scheduler;//暂停任务@RequestMapping("/suspendJob")@ResponseBodypublic String suspendJob(){String jobName="job33";String jobGroup="group33";try{scheduler.pauseJob(JobKey.jobKey(jobName,jobGroup));return "暂停成功";}catch (Exception e){return "暂停失败";}}
}
6.8、重启任务
重点说明:这个时候重启项目,程序会自动的加载QuartzConfig并创建JobDetail和Trigger并自动的向数据库添加数据。
核心添加的qrtz_cron_triggers、qrtz_job_details、qrtz_triggers、qrtz_scheduler_state
说明1:通过scheduler.resumeJob重启任务
说明2:scheduler. resumeJob需要的两个参数是qrtz_job_details表中的JOB_NAME和JOB_GROUP字段的值。
@Controller
public class QuartzController {@ResourceScheduler scheduler;//重启任务@RequestMapping("/resumeJob")@ResponseBodypublic String resumeJob() {String jobName="job33";String jobGroup="group33";try {//恢复任务scheduler.resumeJob(JobKey.jobKey(jobName,jobGroup));return "重启成功";} catch (SchedulerException e) {return "重启失败";}}
}
6.9、删除任务
删除任务会清除数据库中的数据
@Controller
public class QuartzController {@ResourceScheduler scheduler;//删除任务@RequestMapping("/removeJob")@ResponseBodypublic String removeJob() throws SchedulerException {String jobName="job33";String jobGroup="group33";//先暂停任务scheduler.pauseJob(JobKey.jobKey(jobName,jobGroup));//获取任务触发器TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);try {//停止触发器scheduler.pauseTrigger(triggerKey);//移除触发器scheduler.unscheduleJob(triggerKey);//删除任务scheduler.deleteJob(JobKey.jobKey(jobName,jobGroup));return "删除任务成功";} catch (SchedulerException e) {return "删除任务失败";}}
}
6.10、立即启动任务
重点:只会启动一次
@Controller
public class QuartzController {@ResourceScheduler scheduler;//立即启动任务@RequestMapping("/triggerJob")@ResponseBodypublic String triggerJob() {String jobName="job33";String jobGroup="group33";try {scheduler.triggerJob(JobKey.jobKey(jobName,jobGroup));return "启动成功";} catch (SchedulerException e) {return "启动失败";}}
}
6.11、添加任务
【添加任务依赖对象】
对时区不了解,可以看这篇文章:Java中ZonedDateTime使用详解及时间转化(java中获取时区)_java zoneddatetime-CSDN博客
6.11.1、添加任务依赖对象
对时区不了解,可以看这篇文章:Java中ZonedDateTime使用详解及时间转化(java中获取时区)_java zoneddatetime-CSDN博客
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class JobInfo {private String jobName;//job名称private String jobGroup;//job所属组private String triggerName;//触发器名称private String jobDescription;//job任务描述private Map<String,Object> userData;//job携带的用户参数private String cron;//触发器规则private String timeZoneId;//当前时区
}
6.11.2、实现任务添加代码
在项目中使用到了ObjectMapper对象,将map转化成json字符串,不了解可以看文章:
Springboot中解析JSON字符串(jackson库ObjectMapper解析JSON字符串)-CSDN博客
重点:小伙伴需要注意了,创建任务的时候MyQuartzJob任务本身这个是需要提前创建的。
//添加任务
@RequestMapping(value = "/addJob",method = RequestMethod.POST)
@ResponseBody
public String addJob(@RequestBody JobInfo jobInfo) throws JsonProcessingException {System.out.println("======addJob==="+jobInfo.toString());//通过jobKey判断任务是否唯一// jobKey有jobName和jobGroup组成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();}//将map转化成json的工具ObjectMapper objectMapper=new ObjectMapper();//任务详情JobDetail jobDetail = JobBuilder.newJob(MyQuartzJob.class)//jobDetail描述.withDescription(jobInfo.getJobDescription()) //任务描述.usingJobData("userData",objectMapper.writeValueAsString(jobInfo.getUserData())).withIdentity(jobKey) //指定任务.build();//根据cron,TimeZone时区,指定执行计划CronScheduleBuilder builder =CronScheduleBuilder//任务表达式.cronSchedule(jobInfo.getCron()).inTimeZone(TimeZone.getTimeZone(jobInfo.getTimeZoneId()));//触发器Trigger trigger = TriggerBuilder.newTrigger().withIdentity(jobInfo.getTriggerName(), jobInfo.getJobGroup()).startNow().withSchedule(builder).build();//添加任务try {scheduler.scheduleJob(jobDetail, trigger);return "任务添加成功";} catch (SchedulerException e) {System.out.println(e.getMessage());}return "任务添加失败";
}
6.11.3、在postman中的测试
由于我们是在body中传递的数据,在addJob方法接受的参数需要使用@RequestBody注解,
如果不使用请求获取不到body的raw传递的数据。
测试参数:
{
"jobName":"jobName1",
"jobGroup":"jobGroup1",
"triggerName":"triggerName1",
"jobDescription":"三点、六点、九点发优惠卷。",
"cron":"0/5 * * * * ?",
"timeZoneId":"Asia/Shanghai",
"userData":{"name":"123"}
}
数据库参数:
结果输出:
6.12、修改任务
修改任务的本质就是添加任务,只要保证jobName,jobGroup,triggerName等关键参数不变,即可修改数据。
7、自定义任务表
Quartz总共提供了11张表来持久化分布式任务调度的相关信息,总体来说功能强大,但是比较冗余。这个时候很多人希望自己创建一张简单的表,实现任务管理也是可以的。但是这个时候对自定义表的增删改查操作都需要自己写,而无法使用默认提供了功能。
创建信息可以参照如下:
CREATE TABLE `my_job` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`jobName` varchar(50) NOT NULL COMMENT '任务名称',
`jobGroupName` varchar(50) NOT NULL COMMENT '任务组名',`jobTriggerName` varchar(50) NOT NULL COMMENT '触发器名称',
`jobCron` varchar(50) NOT NULL COMMENT '时间规则表达式',
`jobClassPath` varchar(200) NOT NULL COMMENT '类全类型',
`jobDataMap` varchar(100) DEFAULT NULL COMMENT '用户数据',
`jobStatus` int(2) NOT NULL COMMENT '任务状态',
`jobDescribe` varchar(100) DEFAULT NULL COMMENT '任务功能描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
值得注意的是,如果这个时候通过自定义修改了任务状态为体质状态(如停止状态为1),修改之后还需要调用scheduler.pauseJob(JobKey.jobKey(jobName,jobGroup));,任他任务也是。因为我们的停止本质只是修改数据库的值。
8、源码下载
https://download.csdn.net/download/tangshiyilang/88660404