持久化配置
spring:quartz:# quartz 相关属性配置properties:org:quartz:scheduler:instanceName: clusteredSchedulerinstanceId: AUTOjobStore:class: org.quartz.impl.jdbcjobstore.JobStoreTXdriverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegatetablePrefix: QRTZ_isClustered: trueclusterCheckinInterval: 10000useProperties: falsethreadPool:class: org.quartz.simpl.SimpleThreadPoolthreadCount: 10threadPriority: 5threadsInheritContextClassLoaderOfInitializingThread: true#数据库方式job-store-type: jdbc
配置说明
#使用自己的配置文件org.quartz.jobStore.useProperties:true#默认或是自己改名字都行org.quartz.scheduler.instanceName: DefaultQuartzScheduler#如果使用集群,instanceId必须唯一,设置成AUTOorg.quartz.scheduler.instanceId = AUTOorg.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPoolorg.quartz.threadPool.threadCount: 10org.quartz.threadPool.threadPriority: 5org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true#存储方式使用JobStoreTX,也就是数据库org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTXorg.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate#是否使用集群(如果项目只部署到 一台服务器,就不用了)org.quartz.jobStore.isClustered = trueorg.quartz.jobStore.clusterCheckinInterval=20000org.quartz.jobStore.tablePrefix = QRTZ_org.quartz.jobStore.dataSource = myDS#配置数据源#数据库中quartz表的表名前缀org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driverorg.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/quartz?characterEncoding=utf-8org.quartz.dataSource.myDS.user = rootorg.quartz.dataSource.myDS.password = 123456org.quartz.dataSource.myDS.maxConnections = 5
数据库设置
#数据库引擎org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver#数据库连接org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true#数据库用户org.quartz.dataSource.myDS.user = root#数据库密码org.quartz.dataSource.myDS.password = 123456#允许最大连接org.quartz.dataSource.myDS.maxConnections = 5#验证查询sql,可以不设置org.quartz.dataSource.myDS.validationQuery=select 0 from dual
Mysql 表
数据库地址 https://github.com/quartz-scheduler/quartz/blob/master/quartz-core/src/main/resources/org/quartz/impl/jdbcjobstore/tables_mysql_innodb.sql
quartz_innodb.sql
-- in your Quartz properties file, you'll need to set org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate-- 你需要在你的quartz.properties文件中设置org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate-- StdJDBCDelegate说明支持集群,所有的任务信息都会保存到数据库中,可以控制事物,还有就是如果应用服务器关闭或者重启,任务信息都不会丢失,并且可以恢复因服务器关闭或者重启而导致执行失败的任务-- This is the script from Quartz to create the tables in a MySQL database, modified to use INNODB instead of MYISAM-- 这是来自quartz的脚本,在MySQL数据库中创建以下的表,修改为使用INNODB而不是MYISAM-- 你需要在数据库中执行以下的sql脚本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;-- 存储每一个已配置的Job的详细信息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))ENGINE=InnoDB;-- 存储已配置的Trigger的信息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))ENGINE=InnoDB;-- 存储已配置的Simple Trigger的信息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))ENGINE=InnoDB;-- 存储Cron Trigger,包括Cron表达式和时区信息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(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(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))ENGINE=InnoDB;-- Trigger作为Blob类型存储(用于Quartz用户用JDBC创建他们自己定制的Trigger类型,JobStore并不知道如何存储实例的时候)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),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;-- 以Blob类型存储Quartz的Calendar日历信息,quartz可配置一个日历来指定一个时间范围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))ENGINE=InnoDB;-- 存储已暂停的Trigger组的信息CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_GROUP VARCHAR(200) NOT NULL,PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))ENGINE=InnoDB;-- 存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息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))ENGINE=InnoDB;-- 存储少量的有关 Scheduler的状态信息,和别的 Scheduler 实例(假如是用于一个集群中)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))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;
表定义
- qrtz_job_details:记录每个任务的详细信息。
 - qrtz_triggers:记录每个触发器的详细信息。
 - qrtz_corn_triggers:记录cornTrigger的信息。
 - qrtz_scheduler_state:记录 调度器(每个机器节点)的生命状态。
 - qrtz_fired_triggers:记录每个正在执行的触发器。
 - qrtz_locks:记录程序的悲观锁(防止多个节点同时执行同一个定时任务)
 
qrtz_job_details
CREATE TABLE `qrtz_job_details` (`SCHED_NAME` varchar(120) COLLATE utf8_bin NOT NULL COMMENT '调度器名,集群环境中使用,必须使用同一个名称——集群环境下”逻辑”相同的scheduler,默认为QuartzScheduler',`JOB_NAME` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '集群中job的名字',`JOB_GROUP` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '集群中job的所属组的名字',`DESCRIPTION` varchar(250) COLLATE utf8_bin DEFAULT NULL COMMENT '描述',`JOB_CLASS_NAME` varchar(250) COLLATE utf8_bin NOT NULL COMMENT '集群中个note job实现类的完全包名,quartz就是根据这个路径到classpath找到该job类',`IS_DURABLE` varchar(1) COLLATE utf8_bin NOT NULL COMMENT '是否持久化,把该属性设置为1,quartz会把job持久化到数据库中',`IS_NONCONCURRENT` varchar(1) COLLATE utf8_bin NOT NULL COMMENT '是否并行,该属性可以通过注解配置',`IS_UPDATE_DATA` varchar(1) COLLATE utf8_bin NOT NULL,`REQUESTS_RECOVERY` varchar(1) COLLATE utf8_bin NOT NULL COMMENT '当一个scheduler失败后,其他实例可以发现那些执行失败的Jobs,若是1,那么该Job会被其他实例重新执行,否则对应的Job只能释放等待下次触发',`JOB_DATA` blob COMMENT '一个blob字段,存放持久化job对象',PRIMARY KEY (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='存储每一个已配置的 Job 的详细信息';
Job 的状态
如果我们有个需求是统计每个任务的执行次数,那么你会怎么做?
也许你会想到使用上面说到的 JobDataMap,那就让我们尝试下:
任务调度类
// 我们在 JobDataMap 中定义了一个值为 0 的初始值JobDetail jobDetail = JobBuilder.newJob(TestJob.class).usingJobData("executeCount", 0).withIdentity("testJob", "testJobGroup").build();
Job 任务类
@Slf4jpublic class TestJob implements Job {private Integer executeCount;public void setExecuteCount(Integer executeCount) {this.executeCount = executeCount;}@Overridepublic void execute(JobExecutionContext jobExecutionContext) {String data = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));log.info("execute count: {}, current time: {}",++executeCount, data);//将累加的 count 存入JobDataMap中jobExecutionContext.getJobDetail().getJobDataMap().put("executeCount", executeCount);}}/** OUTPUT:execute count: 1, current time: 2020-11-17 22:38:48execute count: 1, current time: 2020-11-17 22:38:52execute count: 1, current time: 2020-11-17 22:38:57**/
按照上面的想法我们写出了这部分代码,但貌似打脸了,结果并没有按照我们预计的发展,是逻辑不对吗,貌似写的也没什么问题。这时你会不会回忆到上面我讲过的一句话:“在调用 execute 方法之前都会创建一个新的 Job 实例”,这就牵引出了 Job 状态的概念:
无状态的 Job
每次调用时都会创建一个新的 JobDataMap
- 有状态的 Job
 
多次 Job 调用可以持有一些状态信息,这些状态信息存储在 JobDataMap 中
那么问题来了,如果让 Job 变成有状态?这个时候我们可以借助一个注解:@PersistJobDataAfterExecution ,加上这个注解后,我们再来试下:
Job 任务类:
@Slf4j@PersistJobDataAfterExecutionpublic class TestJob implements Job {private Integer executeCount;public void setExecuteCount(Integer executeCount) {this.executeCount = executeCount;}@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {String data = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));log.info("execute count: {}, current time: {}",++executeCount, data);//将累加的 count 存入JobDataMap中jobExecutionContext.getJobDetail().getJobDataMap().put("executeCount", executeCount);}}/** OUTPUT:execute count: 1, current time: 2020-11-17 22:28:48execute count: 2, current time: 2020-11-17 22:28:52execute count: 3, current time: 2020-11-17 22:28:57**/
可以看到加了 @PersistJobDataAfterExecution ,我们已经成功达到了我们的目的。
参考
https://www.jianshu.com/p/b94ebb8780fa
https://ld246.com/article/1561629454128
https://segmentfault.com/a/1190000016554033
