SpringBoot整合定时任务@Scheduled与Quartz的方案
作者:张老师技术栈
这篇文章详细介绍了SpringBoot内置的@Scheduled定时任务和企业级调度库Quartz的使用场景与配置方法,涵盖固定间隔与Cron表达式设置,并提供实际开发中的常见问题与解决方案,适合前后端开发人员参考,需要的朋友可以参考下
定时任务是后端系统里的刚需——每天凌晨备份数据库、每月初生成账单报表、定期清理过期数据。SpringBoot 内置了简单的定时任务方案,复杂场景也可以用 Quartz 来搞定。
一、@Scheduled——最简单的定时任务
SpringBoot 自带的方案,不需要任何第三方依赖。
1. 开启定时任务
@SpringBootApplication
@EnableScheduling // 开启定时任务
public class SeckillApplication {
public static void main(String[] args) {
SpringApplication.run(SeckillApplication.class, args);
}
}
2. 编写定时任务
@Component
public class ScheduledTasks {
private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
/**
* 每分钟执行一次
*/
@Scheduled(cron = "0 * * * * ?")
public void task1() {
log.info("定时任务执行: {}", LocalDateTime.now());
}
/**
* 每天晚上 23:30 执行(适合对账、备份)
*/
@Scheduled(cron = "0 30 23 * * ?")
public void dailyReport() {
log.info("生成日报表...");
// 生成报表逻辑
}
/**
* 每月1号凌晨2点执行(适合月度账单、归档)
*/
@Scheduled(cron = "0 0 2 1 * ?")
public void monthlyArchive() {
log.info("月度数据归档...");
// 归档逻辑
}
}
3. Cron 表达式速查
格式:秒 分 时 日 月 周 0 0 12 * * ? → 每天中午12点 0 0/5 * * * ? → 每5分钟一次 0 0 23 * * ? → 每天晚上11点 0 30 9 * * ? → 每天早上9点30分 0 0 2 1 * ? → 每月1号凌晨2点 0 15 10 ? * MON-FRI → 周一到周五上午10点15分 0 0 0/2 * * ? → 每2小时一次 ? 表示不指定(用在『周』或『日』上避免冲突)
推荐工具: 在线 cron 表达式生成器(百度搜"cron 在线"),可视化点选生成。
4. 固定间隔执行
不想写 cron,也可以用固定间隔:
// 上一次执行完毕后,等3秒再执行 @Scheduled(fixedDelay = 3000) // 上一次执行开始后,过5秒再执行(不管上一步有没有完成) @Scheduled(fixedRate = 5000) // 首次延迟10秒后开始执行 @Scheduled(initialDelay = 10000, fixedRate = 5000)
fixedDelay 和 fixedRate 的区别:
fixedDelay: ┌────┐ ┌────┐ ┌────┐ │执行│ │执行│ │执行│ └────┘──└────┘──└────┘ 3秒 3秒 3秒 ← 执行完才开始计时 fixedRate: ┌────┐ ┌────┐ ┌────┐ │执行│ │执行│ │执行│ │────│──│────│──│────│ │ 5秒 │ 5秒 │ ← 不管是否执行完,到点就执行
注意: 如果任务执行时间超过间隔时间,fixedRate 会并发执行,可能导致数据混乱。生产环境建议用 fixedDelay。
5. 异步执行(防止阻塞)
默认定时任务是单线程的——一个任务卡住了,其他任务也跟着卡住。
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("scheduled-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
@Component
public class AsyncScheduledTasks {
@Async("taskExecutor")
@Scheduled(cron = "0 */1 * * * ?")
public void reportCurrentTime() {
System.out.println("当前时间: " + LocalDateTime.now()
+ " 线程: " + Thread.currentThread().getName());
// 即使这里 sleep 了,其他定时任务不受影响
}
@Async("taskExecutor")
@Scheduled(cron = "0 */2 * * * ?")
public void anotherTask() {
System.out.println("另一个定时任务: " + LocalDateTime.now());
}
}
二、Quartz——企业级定时任务
@Scheduled 适合简单场景,如果遇到这些需求就需要 Quartz:
- 任务需要在运行时动态创建/修改/删除(比如用户自行配置推送时间)
- 任务需要持久化到数据库(重启后不丢失)
- 任务需要集群部署(多台机器不重复执行)
1. 集成 Quartz
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>2. 编写任务
public class DataBackupJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
// 获取参数
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
String databaseName = dataMap.getString("databaseName");
System.out.println("备份数据库: " + databaseName + ",时间: " + LocalDateTime.now());
// 执行备份逻辑...
}
}
3. 配置任务调度
@Configuration
public class QuartzConfig {
/**
* 定义任务详情
*/
@Bean
public JobDetail dataBackupJobDetail() {
JobDataMap dataMap = new JobDataMap();
dataMap.put("databaseName", "seckill_db");
return JobBuilder.newJob(DataBackupJob.class)
.withIdentity("dataBackup")
.usingJobData(dataMap)
.storeDurably() // 即使没有触发器也保留
.build();
}
/**
* 定义触发器(每天凌晨2点执行)
*/
@Bean
public Trigger dataBackupTrigger() {
CronScheduleBuilder scheduleBuilder =
CronScheduleBuilder.cronSchedule("0 0 2 * * ?");
return TriggerBuilder.newTrigger()
.forJob(dataBackupJobDetail())
.withIdentity("dataBackupTrigger")
.withSchedule(scheduleBuilder)
.build();
}
}
4. 动态创建任务(运行时新增)
@Service
public class QuartzService {
@Autowired
private Scheduler scheduler;
/**
* 动态添加定时任务
*/
public void addJob(String jobName, String cron, JobDataMap dataMap) throws SchedulerException {
// 创建任务
JobDetail jobDetail = JobBuilder.newJob(DynamicJob.class)
.withIdentity(jobName, "default")
.usingJobData(dataMap)
.build();
// 创建触发器
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(jobName + "Trigger", "default")
.withSchedule(CronScheduleBuilder.cronSchedule(cron))
.build();
// 注册到调度器
scheduler.scheduleJob(jobDetail, trigger);
}
/**
* 修改任务执行时间
*/
public void updateCron(String jobName, String newCron) throws SchedulerException {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName + "Trigger", "default");
CronTrigger newTrigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.withSchedule(CronScheduleBuilder.cronSchedule(newCron))
.build();
scheduler.rescheduleJob(triggerKey, newTrigger);
}
/**
* 暂停任务
*/
public void pauseJob(String jobName) throws SchedulerException {
scheduler.pauseJob(JobKey.jobKey(jobName, "default"));
}
/**
* 恢复任务
*/
public void resumeJob(String jobName) throws SchedulerException {
scheduler.resumeJob(JobKey.jobKey(jobName, "default"));
}
/**
* 删除任务
*/
public void deleteJob(String jobName) throws SchedulerException {
scheduler.deleteJob(JobKey.jobKey(jobName, "default"));
}
}
5. 持久化配置
Quartz 默认把任务信息存在内存里,重启后就丢了。如果要持久化,配置数据库:
spring:
quartz:
job-store-type: jdbc # 使用数据库存储
jdbc:
initialize-schema: always # 自动创建 Quartz 表
properties:
org:
quartz:
scheduler:
instanceName: DefaultQuartzScheduler
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
tablePrefix: QRTZ_
isClustered: false启用 JDBC 存储后,重启应用之前创建的任务自动恢复。
三、@Scheduled vs Quartz 怎么选
| 场景 | 推荐方案 |
|---|---|
| 固定时间执行,不需要改 | ✅ @Scheduled |
| 执行时间可能动态调整 | ✅ Quartz |
| 任务参数需要传参 | ✅ @Scheduled 简单传参 / Quartz 传复杂参数 |
| 需要管理后台控制启停 | ✅ Quartz |
| 集群环境避免重复执行 | ✅ Quartz + 数据库持久化 |
| 简单项目、快速开发 | ✅ @Scheduled 就够了 |
秒杀系统场景:
- 定时初始化库存 → @Scheduled 就够了
- 以后要做订单超时自动取消 → 如果用户量大、需要灵活配置,用 Quartz
四、实际开发中的坑
1. 定时任务默认单线程
# 解决方案一:配线程池
spring:
task:
scheduling:
pool:
size: 5 # 定时任务线程池大小2. 时区问题
// @Scheduled 默认使用服务器时区 // 如果服务器是 UTC,需要指定时区 @Scheduled(cron = "0 0 8 * * ?", zone = "Asia/Shanghai")
3. 任务超时
// Quartz 触发器可以设置超时时间
// 如果任务执行超时,强制终止
trigger = TriggerBuilder.newTrigger()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withMisfireHandlingInstructionFireNow())
.build();
五、完整案例:每天凌晨自动取消超时未支付订单
@Component
public class OrderTimeoutJob {
@Autowired
private OrderService orderService;
/**
* 每30秒检查一次,取消超过10分钟未支付的订单
*/
@Scheduled(fixedDelay = 30000)
public void cancelTimeoutOrders() {
// 找到所有超过10分钟未支付的订单
List<SeckillOrder> timeoutOrders = orderService.lambdaQuery()
.eq(SeckillOrder::getStatus, 0)
.lt(SeckillOrder::getOrderTime, LocalDateTime.now().minusMinutes(10))
.list();
for (SeckillOrder order : timeoutOrders) {
// 取消订单
order.setStatus(2); // 已取消
orderService.updateById(order);
// 归还库存
productService.lambdaUpdate()
.setSql("stock = stock + " + order.getQuantity())
.eq(SeckillProduct::getId, order.getProductId())
.update();
log.info("超时取消订单: {}", order.getOrderNo());
}
}
}
总结: 简单任务用 @Scheduled,复杂调度用 Quartz,不要两个混着用。大部分项目 @Scheduled 完全够用。
以上就是SpringBoot整合定时任务@Scheduled与Quartz的方案的详细内容,更多关于SpringBoot整合定时任务@Scheduled与Quartz的资料请关注脚本之家其它相关文章!
