Quartz 5+ 必须用 JobBuilder 和 TriggerBuilder 构建 JobDetail 与 Trigger,否则无法注入 Spring Bean;持久化需配置 JDBC 存储并手动建表;动态增删任务必须调用 Scheduler API;集群模式下须设 instanceId=AUTO 且启用 isClustered=true。

Quartz 的 JobDetail 和 Trigger 必须用 JobBuilder 与 TriggerBuilder 构建,不能 new
Spring Boot 整合 Quartz 时,很多人直接 new JobDetailImpl() 或手写 SimpleTrigger,结果启动报 ClassNotFoundException 或调度不触发。这是因为 Quartz 5+(Spring Boot 3.x 默认)强制要求通过 Builder 模式创建核心对象,否则无法注入 Spring 管理的依赖(比如 @Autowired 的 service)。
实操建议:
- 始终用
JobBuilder.newJob(MyJob.class),而不是new MyJob() -
Trigger必须用SimpleScheduleBuilder或CronScheduleBuilder配合TriggerBuilder构建,例如:TriggerBuilder.newTrigger().withSchedule(CronScheduleBuilder.cronSchedule("0 0 * * * ?")) - 若需在 Job 中使用 Spring Bean,必须声明为
@PersistJobDataAfterExecution+ 实现StatefulJob(旧版)或更推荐:用JobFactory代理,让 Spring 容器负责实例化
开启持久化必须配 quartz.properties 并替换默认内存存储
默认 Quartz 使用 RAMJobStore,应用一重启,所有定时任务就丢了——这不是 bug,是设计如此。要真正“持久化”,必须切换到数据库存储,并确保 Spring Boot 正确加载配置。
常见错误现象:
- 加了
spring.quartz.job-store-type=jdbc却仍不持久:因为没提供quartz.properties,Spring Boot 会 fallback 到 RAM 存储 - 表结构没建全,启动报
Table 'qrtz_triggers' doesn't exist:Quartz 不自动建表,得手动执行官方 SQL 脚本(如tables_mysql_innodb.sql)
实操建议:
- 在
src/main/resources/下放quartz.properties,至少包含:org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX、org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate、org.quartz.jobStore.dataSource=myDS - 配置
spring.quartz.properties.org.quartz.dataSource.myDS.*连接池参数,别只配spring.datasource—— Quartz 数据源是独立的 - 确认使用的 Quartz 版本对应 SQL 脚本:5.x 用
v5目录下的脚本,别错用 2.x 的
动态增删任务必须调用 Scheduler 实例的 scheduleJob/deleteJob,不能只改数据库
有人想绕过 API,直接 INSERT/DELETE qrtz_job_details 表来增删任务,结果 Quartz 完全无反应。原因很简单:Scheduler 内存中维护着任务缓存和 Trigger 状态机,数据库只是快照,不是实时信源。
使用场景:
- 后台管理页允许用户新增 cron 任务
- 灰度发布时临时停用某类定时任务
实操建议:
- 注入
Scheduler(不是JobDetail),用scheduler.scheduleJob(jobDetail, trigger)注册;删除用scheduler.deleteJob(jobKey) - 修改已存在任务的 cron 表达式:先
unscheduleJob(triggerKey),再用新Trigger重新scheduleJob - 注意事务:如果在 @Transactional 方法里调用
scheduleJob,且该方法抛异常回滚,但任务已注册成功——Quartz 调度不受 Spring 事务控制,必须手动补偿
集群模式下必须设 org.quartz.scheduler.instanceId=AUTO 且禁用 RMI
多实例部署时,若没正确配置集群,会出现重复执行、Trigger 失效、甚至数据库死锁。最典型表现是:两个服务都声称自己“触发了任务”,日志里同一时间出现两条执行记录。
性能 / 兼容性影响:
- 未设
instanceId或设成固定值(如SERVER1),会导致 Quartz 认为多个节点是同一个实例,抢锁逻辑崩溃 - 启用
org.quartz.scheduler.rmi.export=true会暴露 RMI 端口,在云环境基本不可用,且与 Spring Boot 3 的模块化 JDK 冲突
实操建议:
-
quartz.properties中必须有:org.quartz.scheduler.instanceId=AUTO和org.quartz.jobStore.isClustered=true - 确保所有节点使用同一数据库、同一时钟(NTP 同步),否则集群心跳检测会误判节点宕机
- 别碰
rmi相关配置项,默认就是 false,显式设成false更稳妥
复杂点在于:集群不是开个开关就完事,它依赖数据库行级锁和定时心跳(org.quartz.jobStore.clusterCheckinInterval),网络抖动或 DB 延迟高时,容易误剔节点。上线前得压测心跳间隔与 DB 响应时间的关系。










