示例版本
- 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: 0s
jdbc:
# 数据库架构初始化模式(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: true
jobStore:
class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# 表前缀
tablePrefix: QRTZ_
# 是否开启集群
isClustered: true
# 数据源别名(自定义)
dataSource: quartz
# 分布式节点有效性检查时间间隔(毫秒)
clusterCheckinInterval: 10000
useProperties: false
# 线程池配置
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 10
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: 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 {
@Override
public 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 {
@Autowired
private 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 {
@Autowired
private 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";
}
}
}
- 接口调用
-
查询列表
-
添加定时任务
-
删除定时任务
-
执行一次定时任务
-