SpringBoot3.x引入Quartz,持久化到MySQL数据库
示例版本
- SpringBoot3.2.11
- JDK17
- MySQL5.7.38
Cron
在线Cron表达式生成器:https://www.pppet.net
持久化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;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;
项目改造
- 添加依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
- 配置yml文件
# 定时任务配置
quartz:# 任务存储类型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: AUTO# 禁用调度器实例的恢复机制makeSchedulerThreadDaemon: truejobStore: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
- 参数解释
- 在Spring Boot中集成Quartz并使用数据库持久化时,
spring.quartz.jdbc.initialize-schema
是一个重要配置项,它控制Quartz数据库表的初始化行为always
:每次应用启动时都会尝试创建Quartz所需的数据库表(如果表不存在)。never
:永远不会自动初始化表结构。可以执行Quartz发行包的docs/dbTables里的SQL文件来创建。embedded
:仅当使用嵌入式数据库(如H2、HSQLDB、Derby)时才会初始化表结构。
spring.quartz.properties.org.quartz.threadPool.threadCount
是配置Quartz调度器线程池大小的关键参数,它决定了Quartz可以同时执行多少个作业。- 如果使用JDBC JobStore,确保数据库连接池大小 ≥ Quartz线程数
- 不要设置过大,过大的线程数会导致:
- 内存消耗增加
- 线程上下文切换开销增大
- 可能拖慢整个系统
- 典型配置范围:
- 开发环境:5-10
- 中小型生产环境:10-25
- 大型高并发环境:25-50(不建议超过50)
- 在Spring Boot中集成Quartz并使用数据库持久化时,
- 创建测试执行方法
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;import java.util.Date;/*** @Author WangHan* @Date 2025/7/31* @Description 继承 QuartzJobBean (更方便地使用 Spring 的依赖注入)*/@DisallowConcurrentExecution
@Slf4j
public class HelloJob implements Job {@Overridepublic void execute(JobExecutionContext jobExecutionContext) {// 检查是否是恢复执行(应用重启后的执行)if (jobExecutionContext.isRecovering()) {log.info("Skipping HelloJob execution on recovery at: " + new Date());return; // 跳过恢复执行,不执行业务逻辑}log.info("execute HelloJob...." + new Date());// 你的实际业务逻辑放在这里}
}
- 创建service
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;/*** @Author WangHan* @Date 2025/7/31* @Description 定时任务服务*/
@Service
public class QuartzJobService {@Autowiredprivate Scheduler scheduler;/*** 新增定时任务** @param jobName* @param jobGroup* @param triggerName* @param triggerGroup* @param jobClass* @param cron*/public void addJob(String jobName, String jobGroup, String triggerName, String triggerGroup, String jobClass, String cron) throws Exception {Class<? extends Job> jobClazz = (Class<? extends Job>) Class.forName("com.platform.base.job." + jobClass);JobDetail jobDetail = JobBuilder.newJob(jobClazz).withIdentity(jobName, jobGroup).storeDurably().build();CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerName, triggerGroup).startNow().withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();scheduler.scheduleJob(jobDetail, cronTrigger);if (!scheduler.isShutdown()) {scheduler.start();}}/*** 重启定时任务** @param triggerName* @param triggerGroup* @param cron*/public void rescheduleJob(String triggerName, String triggerGroup, String cron) throws Exception {TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroup);CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).startNow().withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();scheduler.rescheduleJob(triggerKey, cronTrigger);}/*** 暂停定时任务** @param jobName* @param jobGroup*/public void pauseJob(String jobName, String jobGroup) throws Exception {scheduler.pauseJob(JobKey.jobKey(jobName, jobGroup));}/*** 恢复定时任务** @param jobName* @param jobGroup*/public void resumeJob(String jobName, String jobGroup) throws Exception {scheduler.resumeJob(JobKey.jobKey(jobName, jobGroup));}/*** 删除定时任务* @param jobName* @param jobGroup* @param triggerName* @param triggerGroup* @throws Exception*/public void deleteJob(String jobName, String jobGroup, String triggerName, String triggerGroup) throws Exception {scheduler.pauseTrigger(TriggerKey.triggerKey(triggerName, triggerGroup));scheduler.unscheduleJob(TriggerKey.triggerKey(triggerName, triggerGroup));scheduler.deleteJob(JobKey.jobKey(jobName, jobGroup));}/*** 查询所有定时任务** @return*/public List<String> listJobs() throws Exception {Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.anyGroup());return jobKeys.stream().map(JobKey::getName).collect(Collectors.toList());}/*** 立即执行指定的任务(不干扰原有的定时任务)** @param jobName 任务名称* @param jobGroup 任务组*/public void triggerJobOnce(String jobName, String jobGroup) throws Exception {JobKey jobKey = new JobKey(jobName, jobGroup);// 检查任务是否存在if (!scheduler.checkExists(jobKey)) {throw new IllegalArgumentException("任务不存在: " + jobName + "." + jobGroup);}// 立即触发执行指定的任务scheduler.triggerJob(jobKey);}
}
- 测试controller
import com.platform.base.service.quartz.QuartzJobService;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.bind.annotation.*;import java.util.ArrayList;
import java.util.List;/*** @Author WangHan* @Date 2025/7/31* @Description 定时任务*/
@Tag(name = "定时任务管理接口", description = "定时任务管理接口")
@EnableTransactionManagement
@RestController
@RequestMapping("/v1/base/pc/quartz")
public class QuartzController {@Autowiredprivate QuartzJobService quartzJobService;/*** 新增定时任务** @param jobName 任务名称,示例:helloJob* @param jobGroup 任务组,示例:helloJobGroup* @param triggerName 触发器名称,示例:helloTrigger* @param triggerGroup 触发器组,示例:helloTriggerGroup* @param jobClass 调度任务,示例:HelloJob* @param cron cron表达式,示例:0 0/1 * * * ?* @return String*/@GetMapping(path = "/add")public String addJob(String jobName, String jobGroup, String triggerName, String triggerGroup, String jobClass, String cron) {try {quartzJobService.addJob(jobName, jobGroup, triggerName, triggerGroup, jobClass, cron);return "ok";} catch (Exception e) {e.printStackTrace();return "fail";}}/*** 暂停任务** @param jobName 任务名称* @param jobGroup 任务组* @return String*/@GetMapping(path = "/pause")public String pauseJob(String jobName, String jobGroup) {try {quartzJobService.pauseJob(jobName, jobGroup);return "ok";} catch (Exception e) {e.printStackTrace();return "fail";}}/*** 恢复任务** @param jobName 任务名称* @param jobGroup 任务组* @return String*/@GetMapping(path = "/resume")public String resumeJob(String jobName, String jobGroup) {try {quartzJobService.resumeJob(jobName, jobGroup);return "ok";} catch (Exception e) {e.printStackTrace();return "fail";}}/*** 重启任务** @param triggerName 触发器名称,示例:helloTrigger* @param triggerGroup 触发器组,示例:helloTriggerGroup* @param cron cron表达式* @return String*/@GetMapping(path = "/reSchedule")public String rescheduleJob(String triggerName, String triggerGroup, String cron) {try {quartzJobService.rescheduleJob(triggerName, triggerGroup, cron);return "ok";} catch (Exception e) {e.printStackTrace();return "fail";}}/*** 删除任务** @param jobName 任务名称,示例:helloJob* @param jobGroup 任务组,示例:helloJobGroup* @param triggerName 触发器名称,示例:helloTrigger* @param triggerGroup 触发器组,示例:helloTriggerGroup* @return String*/@GetMapping(path = "/delete")public String deleteJob(String jobName, String jobGroup, String triggerName, String triggerGroup) {try {quartzJobService.deleteJob(jobName, jobGroup, triggerName, triggerGroup);return "ok";} catch (Exception e) {e.printStackTrace();return "fail";}}/*** 查询所有定时任务** @return List<String>*/@GetMapping(path = "/list")public List<String> listJobs() {try {return quartzJobService.listJobs();} catch (Exception e) {e.printStackTrace();return new ArrayList<String>();}}@GetMapping(path = "/runOnce")public String runOnce(String jobName, String jobGroup) {try {// 临时手动触发执行一次(不影响原有的定时任务)quartzJobService.triggerJobOnce(jobName, jobGroup);return "ok";} catch (Exception e) {e.printStackTrace();return "fail";}}
}
- 接口调用
-
查询列表
-
添加定时任务
-
删除定时任务
-
执行一次定时任务
-