Quartz 是一个功能丰富的开源作业调度库,几乎可以集成到任何 Java 应用程序中 - 从最小的独立应用程序到最大的电子商务系统。Quartz可用于创建简单或复杂的时间表,以执行数十、数百甚至数万个作业;其任务被定义为标准 Java 组件的作业,这些组件几乎可以执行您可能对它们进行编程的任何操作
一、Quartz特征
运行时环境
- Quartz 可以嵌入到另一个独立的应用程序中运行
- Quartz 可以在应用程序服务器(或 servlet 容器)中实例化,并参与 XA 事务
- Quartz可以作为一个独立的程序运行(在其自己的Java虚拟机中),通过RMI使用。
- Quartz 可以实例化为一组独立程序(具有负载平衡和故障转移功能),用于执行作业
任务调度
作业计划在给定触发器发生时运行。触发器几乎可以使用以下任意组合来创建 以下指令:
- 在一天中的某个时间(到毫秒)
- 在一周中的某些日子
- 在每月的某些日子
- 在一年中的某些日子
- 不在已注册日历中列出的某些日期(例如营业假日)
- 重复特定次数
- 重复直到特定时间/日期
- 无限期重复
- 以延迟间隔重复
作业由其创建者命名,也可以组织到命名组中。也可给予触发因素 名称并放入组中,以便在调度程序中轻松组织它们。可以将作业添加到 调度程序一次,但注册了多个触发器。在企业 Java 环境中,作业可以执行其工作 作为分布式 (XA) 事务的一部分。
任务执行
- 任务可以是实现简单作业接口的任何 Java 类,为您的任务可以执行的工作留下无限的可能性。
- 任务类实例可以由 Quartz 或应用程序的框架实例化。
- 当触发器发生时,调度程序会通知零个或多个实现 JobListener 和 TriggerListener 接口的 Java 对象(侦听器可以是简单的 Java 对象、EJB 或 JMS 发布者等)。这些侦听器也会在任务执行后收到通知。
- 当任务完成时,它们返回一个 JobCompletionCode,通知调度程序成功或失败。JobCompletionCode 还可以指示调度程序根据成功/失败代码应执行的任何操作,例如立即重新执行作业。
任务持久性
- Quartz的设计包括一个JobStore接口,可以实现该接口以提供各种用于存储作业的机制。
- 通过使用随附的 JDBCJobStore,所有配置为“非易失性”的作业和触发器都通过 JDBC 存储在关系数据库中。
- 通过使用随附的RAMJobStore,所有作业和触发器都存储在RAM中,因此在程序执行之间不会持久存在 - 但这具有不需要外部数据库的优点。
事务
- Quartz可以通过使用JobStoreCMT(JDBCJobStore的一个子类)参与JTA事务。
- Quartz 可以围绕作业的执行管理 JTA 事务(开始并提交它们),以便作业执行的工作在 JTA 事务中自动发生。
集群
- 故障转移。
- 负载平衡。
- Quartz 的内置集群功能依赖于通过 JDBCJobStore 实现的数据库持久性(如上所述)。
- Quartz 的 Terracotta 扩展提供了集群功能,而无需后备数据库。
侦听器和插件
• 应用程序可以通过实现一个或多个侦听器接口来捕获调度事件以监视或控制作业/触发器行为。
• 插件机制可用于向 Quartz 添加功能,例如保留作业执行的历史记录,或从文件加载作业和触发器定义。
• Quartz 附带了许多“工厂构建”的插件和侦听器
Quartz 的核心类有以下三部分:
- 任务 Job : 需要实现的任务类,实现 execute() 方法,执行后完成任务。
- 触发器 Trigger : 包括 SimpleTrigger 和 CronTrigger。
- 调度器 Scheduler : 任务调度器,负责基于 Trigger触发器,来执行 Job任务
- JobBuilder - 用于定义/构建 JobDetail 实例,这些实例定义作业的实例。
- TriggerBuilder - 用于定义/构建触发器实例
- JobDetail:Quartz 中需要执行的任务详情,包括了任务的唯一标识和具体要执行的任务,可以通过 JobDataMap 往任务中传递数据
调度程序的生命周期受其创建、通过调度程序工厂和 对其 shutdown() 方法的调用。创建后,可以使用调度程序界面添加、删除和列出 作业和触发器,并执行其他与计划相关的操作(例如暂停触发器)。然而, 调度程序在使用 start() 方法启动之前不会实际作用于任何触发器(执行作业)。调度程序的生命周期受其创建、通过调度程序工厂和 对其 shutdown() 方法的调用。创建后,可以使用调度程序界面添加、删除和列出 作业和触发器,并执行其他与计划相关的操作(例如暂停触发器)。然而, 调度程序在使用 start() 方法启动之前不会实际作用于任何触发器(执行作业):
// define the job and tie it to our HelloJob class JobDetail job = newJob(HelloJob.class) .withIdentity("myJob", "group1") // name "myJob", group "group1" .build(); // Trigger the job to run now, and then every 40 seconds Trigger trigger = newTrigger() .withIdentity("myTrigger", "group1") .startNow() .withSchedule(simpleSchedule() .withIntervalInSeconds(40) .repeatForever()) .build(); // Tell quartz to schedule the job using our trigger sched.scheduleJob(job, trigger);
生成作业定义的代码块使用从 JobBuilder 类静态导入的方法。同样,生成触发器的代码块使用导入的方法 来自 TriggerBuilder 类 - 以及来自 SimpleScheduleBuilder 类。
DSL 的静态导入可以通过如下导入语句来实现:
import static org.quartz.JobBuilder.*; import static org.quartz.SimpleScheduleBuilder.*; import static org.quartz.CronScheduleBuilder.*; import static org.quartz.CalendarIntervalScheduleBuilder.*; import static org.quartz.TriggerBuilder.*; import static org.quartz.DateBuilder.*;
各种“ScheduleBuilder”类具有与定义不同类型的计划相关的方法。
DateBuilder 类包含各种方法,用于轻松构造 java.util.Date 实例 特定时间点(例如表示下一个偶数小时的日期 - 换句话说,如果是,则为 10:00:00 目前为9:43:27)
任务和触发器
ob 是实现 Job 接口的类,它只有一个简单的方法:
作业界面
package org.quartz; public interface Job { public void execute(JobExecutionContext context) throws JobExecutionException; }
当任务的触发器触发时(稍后会详细介绍),execute(..) 方法由调度程序的 工作线程。传递给此方法的 JobExecutionContext 对象提供作业 实例,其中包含有关其“运行时”环境的信息 - 执行它的调度程序的句柄,以及 触发执行的触发器、作业的 JobDetail 对象和其他一些项。
JobDetail 对象由 Quartz 客户端(您的程序)在添加作业时创建 到调度程序。它包含任务的各种属性设置,以及一个任务数据映射,它可以用于存储任务类的给定实例的状态信息。它本质上是工作的定义 实例,并在下一课中进一步详细讨论。
触发器对象用于触发任务的执行(或“触发”)。当您愿意时 计划任务时,实例化触发器并“调整”其属性以提供所需的计划。触发器 可能还具有与之关联的 JobDataMap - 这对于将特定于 触发扳机。石英船有几种不同的触发类型,但最常用的类型是 SimpleTrigger 和 CronTrigger。
如果您需要“一次性”执行(只需在给定时刻单次执行任务),SimpleTrigger 非常方便 time),或者如果您需要在给定时间触发任务,并让它重复 N 次,两次执行之间的延迟为 T。 如果您希望根据类似日历的计划进行触发,例如“每周五中午”,CronTrigger 很有用 或“每月 10 日 10:15”。
为什么选择任务和触发器?许多任务计划程序没有单独的任务和触发器概念。有些人定义“工作” 作为简单的执行时间(或计划)以及一些小的任务标识符。其他人很像 石英的作业和触发器对象。在开发 Quartz 时,我们认为在 计划和要按该计划执行的工作。这(在我们看来)有很多好处。
例如,可以在独立于触发器的任务计划程序中创建和存储作业,并且许多触发器可以 与同一作业关联。这种松散耦合的另一个好处是能够配置保留在 计划程序在其关联的触发器过期后,以便以后可以重新计划,而不必 重新定义它。它还允许您修改或替换触发器,而无需重新定义其关联的任务。
身份
作业和触发器在向 Quartz 调度程序注册时会获得识别键。乔布斯和 触发器(JobKey 和 TriggerKey)允许将它们放入“组”中,这对于组织您的任务和 触发器分为“报告任务”和“维护作业”等类别。任务键的名称部分或 触发器在组中必须是唯一的 - 换句话说,任务或触发器的完整密钥(或标识符)是 名称和组的复合体。
二、配置详解
1、主配置(主调度程序设置、事务的配置)
配置主调度程序设置
这些属性配置调度程序的标识以及各种其他“顶级”设置。
属性名称 |
要求 |
类型 |
默认值 |
org.quartz.scheduler.instanceName |
不 |
字符串 |
“quartz调度程序” |
org.quartz.scheduler.instanceId |
不 |
字符串 |
“NON_CLUSTERED” |
org.quartz.scheduler.instanceIdGenerator.class |
不 |
字符串(类名) |
org.quartz.simpl .SimpleInstanceIdGenerator |
org.quartz.scheduler.threadName |
不 |
字符串 |
实例名称 + “_QuartzSchedulerThread” |
org.quartz.scheduler.makeSchedulerThreadDaemon |
不 |
布尔 |
假 |
org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer |
不 |
布尔 |
假 |
org.quartz.scheduler.idleWaitTime |
不 |
长 |
30000 |
org.quartz.scheduler.dbFailureRetryInterval |
不 |
长 |
15000 |
org.quartz.scheduler.classLoadHelper.class |
不 |
字符串(类名) |
org.quartz.simpl .级联类加载助手 |
org.quartz.scheduler.jobFactory.class |
不 |
字符串(类名) |
org.quartz.simpl.PropertySettingJobFactory |
org.quartz.context.key.SOME_KEY |
不 |
字符串 |
没有 |
org.quartz.scheduler.userTransactionURL |
不 |
字符串 (网址) |
'java:comp/UserTransaction' |
org.quartz.scheduler.wrapJobExecutionInUserTransaction |
不 |
布尔 |
假 |
org.quartz.scheduler.skipUpdateCheck |
不 |
布尔 |
假 |
org.quartz.scheduler.batchTriggerAcquisition MaxCount |
不 |
国际 |
1 |
org.quartz.scheduler.batchTriggerAcquisition FireAheadTimeWindow |
不 |
长 |
0 |
org.quartz.scheduler.instanceName
可以是任何字符串,并且该值对调度程序本身没有意义 - 而是用作客户端的机制 用于在同一程序中使用多个实例时区分调度程序的代码。如果您使用的是群集 功能,您必须对集群中“逻辑上”相同的调度程序的每个实例使用相同的名称。
org.quartz.scheduler.instanceId
可以是任何字符串,但对于所有调度程序必须是唯一的,就好像它们是 簇。如果您希望为您生成 Id,则可以使用值“AUTO”作为实例 ID。或值 “SYS_PROP”,如果您希望值来自系统属性“org.quartz.scheduler.instanceId”。
org.quartz.scheduler.instanceIdGenerator.class
仅当 org.quartz.scheduler.instanceId 设置为“AUTO”时才使用。默认为 “org.quartz.simpl.SimpleInstanceIdGenerator”,它根据主机名和时间戳生成实例 ID。 其他 IntanceIdGenerator 实现包括 SystemPropertyInstanceIdGenerator(获取实例 ID 来自系统属性“org.quartz.scheduler.instanceId”,以及使用 本地主机名(InetAddress.getLocalHost).getHostName())。您还可以实现 InstanceIdGenerator。 接口你自己。
org.quartz.scheduler.threadName
可以是作为 java 线程的有效名称的任何字符串。如果未指定此属性,线程将收到 调度程序的名称(“org.quartz.scheduler.instanceName”)加上附加的字符串“_QuartzSchedulerThread”。
org.quartz.scheduler.makeSchedulerThreadDaemon
一个布尔值(“true”或“false”),它指定调度程序的主线程应该是守护程序线程还是 不。另请参阅 org.quartz.scheduler.makeSchedulerThreadDaemon 属性,以调整 if 这是您正在使用的线程池实现(很可能是这种情况)。SimpleThreadPoolorg.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer
一个布尔值(“true”或“false”),它指定 Quartz 生成的线程是否将继承上下文 初始化线程(初始化 Quartz 实例的线程)的类加载器。这将影响石英主 调度线程、JDBCJobStore 的 misfire 处理线程(如果使用 JDBCJobStore)、集群恢复线程(如果使用 使用集群),以及 SimpleThreadPool 中的线程(如果使用 SimpleThreadPool)。将此值设置为“true”可能会 帮助类装入、JNDI 查找以及与在应用程序服务器中使用 Quartz 相关的其他问题。
org.quartz.scheduler.idleWaitTime
是计划程序在重新查询可用触发器之前等待的时间量(以毫秒为单位),当 否则调度程序处于空闲状态。通常,您不必“调整”此参数,除非您使用的是 XA 事务, 并且存在延迟触发应立即触发的触发器的问题。小于 5000 毫秒的值不是 建议使用,因为它会导致过多的数据库查询。小于 1000 的值是不合法的。
org.quartz.scheduler.dbFailureRetryInterval
计划程序在检测到丢失 作业存储中的连接(例如,与数据库的连接)。这个参数在使用时显然不是很有意义 RamJobStore.
org.quartz.scheduler.classLoadHelper.class
默认使用最健壮的方法,即使用 “org.quartz.simpl.CascadingClassLoadHelper” 类 - 其中 turn 使用每隔一个 ClassLoadHelper 类,直到一个类工作。您可能不应该发现需要指定任何其他 类,尽管应用程序服务器中似乎发生了奇怪的事情。所有电流可能 ClassLoadHelper 实现可以在 org.quartz.simpl 包中找到。
org.quartz.scheduler.jobFactory.class
要使用的作业工厂的类名。JobFatcory 负责生成 JobClass 的实例。 默认值是“org.quartz.simpl.PropertySettingJobFactory”,它只是在类上调用 newInstance() 来生成 每次即将执行时都有一个新实例。PropertySettingJobFactory 也反射性地 使用 SchedulerContext 和 Job and Trigger JobDataMap 的内容设置作业的 Bean 属性。
org.quartz.context.key.SOME_KEY
表示将作为字符串放入“调度程序上下文”的名称-值对。(请参阅 Scheduler.getContext())。 例如,设置“org.quartz.context.key.MyKey = MyValue”将执行等效于 scheduler.getContext().put(“MyKey”, “MyValue”).
注意: 除非使用的是 JTA 事务,否则配置文件中应省略与事务相关的属性。
org.quartz.scheduler.userTransactionURL
应设置为 JNDI URL,Quartz 可以在该 URL 上找到应用程序服务器的 UserTransaction 管理器。默认 值(如果未指定)为 “java:comp/UserTransaction” - 它适用于几乎所有的应用程序服务器。网络圈 用户可能需要将此属性设置为“JTA/UserTransaction”。仅当 Quartz 配置为使用 JobStoreCMT 和 org.quartz.scheduler.wrapJobExecutionInUserTransaction 设置为 true。
org.quartz.scheduler.wrapJobExecutionInUserTransaction
如果希望 Quartz 在对作业调用 execute 之前启动用户事务,则应设置为 “true”。Tx将 在作业的执行方法完成后提交,并在更新作业数据映射(如果它是有状态作业)之后提交。这 默认值为“假”。您可能还对使用 @ExecuteInJTATransaction 注释感兴趣 在您的作业类上,这使您可以控制单个作业是否应启动 JTA 事务 - 而此属性会导致它对所有作业发生。
org.quartz.scheduler.skipUpdateCheck
是否跳过运行快速 Web 请求以确定是否有可用于 Quartz 的更新版本 下载。如果检查运行,并找到更新,它将在 Quartz 的日志中报告为可用。你 还可以使用系统属性“org.terracotta.quartz.skipUpdateCheck=true”禁用更新检查(其中 您可以在系统环境中设置,也可以在 java 命令行上设置为 -D)。建议您禁用 生产部署的更新检查。
org.quartz.scheduler.batchTriggerAcquisitionMaxCount
允许调度程序节点一次获取(用于触发)的最大触发器数。默认值 为 1。数字越大,触发效率越高(在需要 一次全部触发) - 但代价是群集节点之间可能存在不平衡的负载。如果此值 属性设置为 > 1,并使用 JDBC JobStore,然后属性“org.quartz.jobStore.acquireTriggersWithinLock” 必须设置为“true”以避免数据损坏。
org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow
允许在预定触发时间之前获取和触发触发器的时间量(以毫秒为单位)。 默认值为 0。数字越大,批量获取触发触发器的可能性就越大 并一次触发多个触发器 - 代价是触发器时间表未得到精确遵守(触发器可能会 早点开这个量)。在调度程序具有非常大的情况下,这可能很有用(为了性能起见) 需要同时或接近同时触发的触发器数。
2、cron表达式
cron的表达式是字符串,实际上是由七子表达式,描述个别细节的时间表。
字段名 |
允许的值 |
允许的特殊字符 |
秒 |
0-59 |
, - * / |
分 |
0-59 |
, - * / |
小时 |
0-23 |
, - * / |
日 |
1-31 |
, - * ? / L W C |
月 |
1-12 or JAN-DEC |
, - * / |
周几 |
1-7 or SUN-SAT |
, - * ? / L C # |
年 (可选字段) |
empty, 1970-2099 |
, - * / |
“*” 代表整个时间段
“?”字符:表示不确定的值
“,”字符:指定数个值
“-”字符:指定一个值的范围
“/”字符:指定一个值的增加幅度。n/m表示从n开始,每次增加m
“L”字符:用在日表示一个月中的最后一天,用在周表示该月最后一个星期X
“W”字符:指定离给定日期最近的工作日(周一到周五)
“#”字符:表示该月第几个周X。6#3表示该月第3个周五
eg:
每隔5秒执行一次:*/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月1号凌晨1点执行一次:0 0 1 1 * ?
每月最后一天23点执行一次:0 0 23 L * ?
每周星期天凌晨1点实行一次:0 0 1 ? * L
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?
Corn表达式在线验证:http://cron.qqe2.com/
构建一个将在每天上午 10:42 触发的触发器:
trigger = newTrigger() .withIdentity("trigger3", "group1") .withSchedule(dailyAtHourAndMinute(10, 42)) .forJob(myJobKey) .build();
三、开发详解
1、配置文件 pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>
2、数据库准备
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;
3、quartz.properties
默认情况下,Quartz 会加载 classpath 下的 quartz.properties 作为配置文件。如果找不到,则会使用 quartz 框架自己 jar 包下 org/quartz 底下的 quartz.properties 文件
org.quartz.scheduler.instanceId=AUTO org.quartz.scheduler.instanceName=DefaultQuartzScheduler #如果您希望Quartz Scheduler通过RMI作为服务器导出本身,则将“rmi.export”标志设置为true #在同一个配置文件中为'org.quartz.scheduler.rmi.export'和'org.quartz.scheduler.rmi.proxy'指定一个'true'值是没有意义的,如果你这样做'export'选项将被忽略 org.quartz.scheduler.rmi.export=false #如果要连接(使用)远程服务的调度程序,则将“org.quartz.scheduler.rmi.proxy”标志设置为true。您还必须指定RMI注册表进程的主机和端口 - 通常是“localhost”端口1099 org.quartz.scheduler.rmi.proxy=false org.quartz.scheduler.wrapJobExecutionInUserTransaction=false #实例化ThreadPool时,使用的线程类为SimpleThreadPool org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool #threadCount和threadPriority将以setter的形式注入ThreadPool实例 #并发个数 如果你只有几个工作每天触发几次 那么1个线程就可以,如果你有成千上万的工作,每分钟都有很多工作 那么久需要50-100之间. #只有1到100之间的数字是非常实用的 org.quartz.threadPool.threadCount=5 #优先级 默认值为5 org.quartz.threadPool.threadPriority=5 #可以是“true”或“false”,默认为false org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true #在被认为“misfired”(失火)之前,调度程序将“tolerate(容忍)”一个Triggers(触发器)将其下一个启动时间通过的毫秒数。默认值(如果您在配置中未输入此属性)为60000(60秒) org.quartz.jobStore.misfireThreshold=5000 # 默认存储在内存中,RAMJobStore快速轻便,但是当进程终止时,所有调度信息都会丢失 #org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore #持久化方式,默认存储在内存中,此处使用数据库方式 org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX #您需要为JobStore选择一个DriverDelegate才能使用。DriverDelegate负责执行特定数据库可能需要的任何JDBC工作 # StdJDBCDelegate是一个使用“vanilla”JDBC代码(和SQL语句)来执行其工作的委托,用于完全符合JDBC的驱动程序 org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate #可以将“org.quartz.jobStore.useProperties”配置参数设置为“true”(默认为false),以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串, #因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题 org.quartz.jobStore.useProperties=true #表前缀 org.quartz.jobStore.tablePrefix=QRTZ_ #数据源别名,自定义 org.quartz.jobStore.dataSource=qzDS #使用阿里的druid作为数据库连接池 org.quartz.dataSource.qzDS.connectionProvider.class=org.example.config.DruidPoolingconnectionProvider org.quartz.dataSource.qzDS.URL=jdbc:mysql://127.0.0.1:3306/test_quartz?characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=UTC org.quartz.dataSource.qzDS.user=root org.quartz.dataSource.qzDS.password=123456 org.quartz.dataSource.qzDS.driver=com.mysql.jdbc.Driver org.quartz.dataSource.qzDS.maxConnections=10
4、实例代码
1、quartz 配置类 QuartzConfig,初始化Quartz分布式集群相关配置,包括集群设置、数据库等
@Configuration public class QuartzConfig { @Autowired private DataSource dataSource; /** * 调度器 * * @return * @throws Exception */ @Bean public Scheduler scheduler() throws Exception { Scheduler scheduler = schedulerFactoryBean().getScheduler(); return scheduler; } /** * Scheduler工厂类 * * @return * @throws IOException */ @Bean public SchedulerFactoryBean schedulerFactoryBean() throws IOException { SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setSchedulerName("Cluster_Scheduler"); factory.setDataSource(dataSource); factory.setApplicationContextSchedulerContextKey("applicationContext"); factory.setTaskExecutor(schedulerThreadPool()); //factory.setQuartzProperties(quartzProperties()); factory.setStartupDelay(10);// 延迟10s执行 return factory; } /** * 配置Schedule线程池 * * @return */ @Bean public Executor schedulerThreadPool() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(Runtime.getRuntime().availableProcessors()); executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors()); executor.setQueueCapacity(Runtime.getRuntime().availableProcessors()); return executor; } }
2、任务类相关代码
import java.text.ParseException; import java.util.Date; import org.quartz.CronExpression; public class CronUtils { /** * 返回一个布尔值代表一个给定的Cron表达式的有效性 * * @param cronExpression Cron表达式 * @return boolean 表达式是否有效 */ public static boolean isValid(String cronExpression) { return CronExpression.isValidExpression(cronExpression); } /** * 返回一个字符串值,表示该消息无效Cron表达式给出有效性 * * @param cronExpression Cron表达式 * @return String 无效时返回表达式错误描述,如果有效返回null */ public static String getInvalidMessage(String cronExpression) { try { new CronExpression(cronExpression); return null; } catch (ParseException pe) { return pe.getMessage(); } } /** * 返回下一个执行时间根据给定的Cron表达式 * * @param cronExpression Cron表达式 * @return Date 下次Cron表达式执行时间 */ public static Date getNextExecution(String cronExpression) { try { CronExpression cron = new CronExpression(cronExpression); return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis())); } catch (ParseException e) { throw new IllegalArgumentException(e.getMessage()); } }
public class ScheduleConstants { public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME"; /** 执行目标key */ public static final String TASK_PROPERTIES = "TASK_PROPERTIES"; /** 默认 */ public static final String MISFIRE_DEFAULT = "0"; /** 立即触发执行 */ public static final String MISFIRE_IGNORE_MISFIRES = "1"; /** 触发一次执行 */ public static final String MISFIRE_FIRE_AND_PROCEED = "2"; /** 不触发立即执行 */ public static final String MISFIRE_DO_NOTHING = "3"; public enum Status { /** * 正常 */ NORMAL("0"), /** * 暂停 */ PAUSE("1"); private String value; private Status(String value) { this.value = value; } public String getValue() { return value; } }
定义QuartzJob类:
public class QuartzJob implements Serializable{ private static final long serialVersionUID = 1L; /** 任务ID */private Long jobId; /** 任务名称 */private String jobName; /** 任务组名 */private String jobGroup; /** 调用目标字符串 */private String invokeTarget; /** cron执行表达式 */private String cronExpression; /** cron计划策略 */private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT; /** 是否并发执行(0允许 1禁止) */private String concurrent; /** 任务状态(0正常 1暂停) */private String status; public Long getJobId(){ return jobId; } public void setJobId(Long jobId){ this.jobId = jobId; } @NotBlank(message = "任务名称不能为空") @Size(min = 0, max = 64, message = "任务名称不能超过64个字符") public String getJobName(){ return jobName; } public void setJobName(String jobName){ this.jobName = jobName; } public String getJobGroup(){ return jobGroup; } public void setJobGroup(String jobGroup){ this.jobGroup = jobGroup; } @NotBlank(message = "调用目标字符串不能为空") @Size(min = 0, max = 500, message = "调用目标字符串长度不能超过500个字符") public String getInvokeTarget(){ return invokeTarget; } publicvoidsetInvokeTarget(String invokeTarget){ this.invokeTarget = invokeTarget; } @NotBlank(message = "Cron执行表达式不能为空") @Size(min = 0, max = 255, message = "Cron执行表达式不能超过255个字符") public String getCronExpression(){ return cronExpression; } public void setCronExpression(String cronExpression){ this.cronExpression = cronExpression; } @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") public Date getNextValidTime(){ if (StringUtils.isNotEmpty(cronExpression)) { return CronUtils.getNextExecution(cronExpression); } returnnull; } public String getMisfirePolicy(){ return misfirePolicy; } public void setMisfirePolicy(String misfirePolicy){ this.misfirePolicy = misfirePolicy; } public String getConcurrent(){ return concurrent; } public void setConcurrent(String concurrent){ this.concurrent = concurrent; } public String getStatus(){ return status; } public void setStatus(String status){ this.status = status; } @Override public String toString(){ returnnew ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("jobId", getJobId()) .append("jobName", getJobName()) .append("jobGroup", getJobGroup()) .append("cronExpression", getCronExpression()) .append("nextValidTime", getNextValidTime()) .append("misfirePolicy", getMisfirePolicy()) .append("concurrent", getConcurrent()) .append("status", getStatus()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .append("remark", getRemark()) .toString(); } }
3、定义具体任务job
public class TaskJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { JobKey key = context.getJobDetail().getKey(); JobDataMap dataMap = context.getMergedJobDataMap(); // Note the difference from the previous example String jobSays = dataMap.getString("jobSays"); float myFloatValue = dataMap.getFloat("myFloatValue"); ArrayList state = (ArrayList)dataMap.get("myStateData"); state.add(new Date()); System.err.println("Instance " + key + " of TaskJob says: " + jobSays + ", and val is: " + myFloatValue); } }
4、业务 Service 层
定义QuartzService
import com.alibaba.fastjson2.JSONObject; public interface QuartzService { /** * 创建Job * @param job */ Boolean addJob(QuartzJob job); /** * 执行Job * @param job */ Boolean runJob(JobEntity job); /** * 修改Job * @param job */ Boolean updateJob(QuartzJob job); /** * 暂定Job * @param job */ Boolean pauseJob(QuartzJob job); /** * 唤醒Job * @param job */ Boolean resumeJob(QuartzJob job); /** * 删除Job * @param job */ Boolean deleteJob(QuartzJob job); /** * 获取Job * @param job */ JSONObject queryJob(QuartzJob job); }
实现QuartzServiceImpl
import java.util.List; import org.quartz.CronScheduleBuilder; import org.quartz.CronTrigger; import org.quartz.Job; import org.quartz.JobBuilder; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.Trigger.TriggerState; import org.quartz.TriggerBuilder; import org.quartz.TriggerKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import com.alibaba.fastjson2.JSONObject; import com.nuctech.uranus.common.UranusConstant; import com.nuctech.uranus.util.StringUtil; @Service public class QuartzServiceImpl implements QuartzService{ @Autowired private Scheduler scheduler; private Logger log = LoggerFactory.getLogger(QuartzServiceImpl.class); /** * jobName -- task_code */ @Transactional @SuppressWarnings("unchecked") @Override public Boolean addJob(QuartzJob job) { String jName = job.getJobName(); String jGroup = job.getJobGroup(); String tName = job.getTriggerName(); String tGroup = job.getTriggerGroup(); String cron = job.getCronExpression(); try { // 构建JobDetail JobDetail jobDetail = JobBuilder.newJob(TaskJob.class) .withIdentity(jName, jGroup) .build(); // 按新的cronExpression表达式构建一个新的trigger CronTrigger trigger = TriggerBuilder.newTrigger() .withIdentity(tName, tGroup) .startNow() .withSchedule(CronScheduleBuilder.cronSchedule(cron)) .build(); // 启动调度器 scheduler.start(); scheduler.scheduleJob(jobDetail, trigger); return true; } catch (Exception e) { log.info("创建定时任务失败" + e); } return false; } @Override public Boolean runJob(QuartzJob job) { return null; } @Override public Boolean updateJob(QuartzJob job) { return null; } /** * JobName * JobGroup */ @Transactional @Override public Boolean pauseJob(QuartzJob job) { try { String jName = job.getJobName(); String jGroup = job.getJobGroup(); String tName = job.getTriggerName(); String tGroup = job.getTriggerGroup(); String cron = job.getCronExpression(); scheduler.pauseJob(JobKey.jobKey(jName, jGroup)); return true; } catch (SchedulerException e) { } return false; } @Transactional @Override public Boolean resumeJob(QuartzJob job) { try { String jName = job.getJobName(); String jGroup = job.getJobGroup(); String tName = job.getTriggerName(); String tGroup = job.getTriggerGroup(); String cron = job.getCronExpression(); scheduler.resumeJob(JobKey.jobKey(jName, jGroup)); return true; } catch (SchedulerException e) { } return false; } @Override public Boolean rescheduleJob(QuartzJob job) { try { String jName = job.getJobName(); String jGroup = job.getJobGroup(); String tName = job.getTriggerName(); String tGroup = job.getTriggerGroup(); String cron = job.getCronExpression(); TriggerKey triggerKey = TriggerKey.triggerKey(jName, jGroup); // 表达式调度构建器 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); // 按新的cronExpression表达式重新构建trigger trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); // 按新的trigger重新设置job执行,重启触发器 scheduler.rescheduleJob(triggerKey, trigger); return true; } catch (SchedulerException e) { } return false; } @Transactional @Override public Boolean deleteJob(QuartzJob job) { try { String jName = job.getJobName(); String jGroup = job.getJobGroup(); String tName = job.getTriggerName(); String tGroup = job.getTriggerGroup(); String cron = job.getCronExpression(); scheduler.pauseTrigger(TriggerKey.triggerKey(jName, jGroup)); scheduler.unscheduleJob(TriggerKey.triggerKey(jName, jGroup)); scheduler.deleteJob(JobKey.jobKey(jName, jGroup)); return true; } catch (SchedulerException e) { } return false; } }
5、调用层(Controller)
@Autowired private QuartzService quartzService; /** * 新增定时任务 * * @param jName 任务名称 * @param jGroup 任务组 * @param tName 触发器名称 * @param tGroup 触发器组 * @param cron cron表达式 * @return ResultMap */ @PostMapping(path = "/addjob") @ResponseBody public ResultMap addjob(String jName, String jGroup, String tName, String tGroup, String cron) { try { QuartzJob job=new QuartzJob(); job.setJobName(jName); job.setJobGroup(jGroup); job.setTriggerName(tName); job.setTriggerGroup(tGroup); quartzService.addJob(job); return new ResultMap().success().message("添加任务成功"); } catch (Exception e) { e.printStackTrace(); return new ResultMap().error().message("添加任务失败"); } } /** * 暂停任务 * * @param jName 任务名称 * @param jGroup 任务组 * @return ResultMap */ @PostMapping(path = "/pausejob") @ResponseBody public ResultMap pausejob(String jName, String jGroup) { QuartzJob job=new QuartzJob(); job.setJobName(jName); job.setJobGroup(jGroup); try { quartzService.pauseJob(job); return new ResultMap().success().message("暂停任务成功"); } catch (Exception e) { e.printStackTrace(); return new ResultMap().error().message("暂停任务失败"); } } /** * 恢复任务 * * @param jName 任务名称 * @param jGroup 任务组 * @return ResultMap */ @PostMapping(path = "/resumejob") @ResponseBody public ResultMap resumejob(String jName, String jGroup) { QuartzJob job=new QuartzJob(); job.setJobName(jName); job.setJobGroup(jGroup); try { quartzService.resumeJob(job); return new ResultMap().success().message("恢复任务成功"); } catch (Exception e) { e.printStackTrace(); return new ResultMap().error().message("恢复任务失败"); } } /** * 重启任务 * * @param jName 任务名称 * @param jGroup 任务组 * @param cron cron表达式 * @return ResultMap */ @PostMapping(path = "/reschedulejob") @ResponseBody public ResultMap rescheduleJob(String jName, String jGroup, String cron) { QuartzJob job=new QuartzJob(); job.setJobName(jName); job.setJobGroup(jGroup); try { quartzService.rescheduleJob(job); return new ResultMap().success().message("重启任务成功"); } catch (Exception e) { e.printStackTrace(); return new ResultMap().error().message("重启任务失败"); } } /** * 删除任务 * * @param jName 任务名称 * @param jGroup 任务组 * @return ResultMap */ @PostMapping(path = "/deletejob") @ResponseBody public ResultMap deletejob(String jName, String jGroup) { QuartzJob job=new QuartzJob(); job.setJobName(jName); job.setJobGroup(jGroup); try { quartzService.deleteJob(job); return new ResultMap().success().message("删除任务成功"); } catch (Exception e) { e.printStackTrace(); return new ResultMap().error().message("删除任务失败"); } }
详细信息请见官网:http://www.quartz-scheduler.org/overview/
📢文章下方有交流学习区!一起学习进步!也可以前往官网,加入官方微信交流群💪💪💪
📢创作不易,如果觉得文章不错,可以点赞👍收藏📁评论📒
📢你的支持和鼓励是我创作的动力❗❗❗
官网:Doker 多克;官方旗舰店:官方旗舰店-Doker 多克-淘宝网 全品优惠