SpringBoot整合定时任务@Scheduled与Quartz的方案

 更新时间:2026年07月01日 09:07:08   作者:张老师技术栈  
这篇文章详细介绍了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的资料请关注脚本之家其它相关文章!

相关文章

  • SpringBoot使用@PathVariable进行数据校验的流程步骤

    SpringBoot使用@PathVariable进行数据校验的流程步骤

    在SpringBoot项目中,我们经常需要从 URL 中获取参数并进行相关的数据校验,而@PathVariable注解就是一种非常方便的方式,可以让我们在方法参数中直接获取URL中的参数,并进行数据校验,本文将介绍如何使用@PathVariable注解进行数据校验
    2023-06-06
  • java中Serializable接口作用详解

    java中Serializable接口作用详解

    这篇文章主要为大家详细介绍了java中Serializable接口作用,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-05-05
  • SpringBoot2开发从0开始Spring Initailizr初始化

    SpringBoot2开发从0开始Spring Initailizr初始化

    这篇文章主要为大家介绍了SpringBoot2从0开始lombok、devtools、Spring Initailizr的开发技巧,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-05-05
  • 学习java编程后可以走哪些职业道路

    学习java编程后可以走哪些职业道路

    在本篇文章里给大家介绍了关于学习java后的职业道路,以及需要学习的相关知识内容,有兴趣的朋友们可以跟着学习下。
    2022-11-11
  • Spring-boot oauth2使用RestTemplate进行后台自动登录的实现

    Spring-boot oauth2使用RestTemplate进行后台自动登录的实现

    这篇文章主要介绍了Spring-boot oauth2使用RestTemplate进行后台自动登录的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • SpringBoot集成Project Loom实战

    SpringBoot集成Project Loom实战

    本文主要介绍了SpringBoot集成Project Loom实战,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-04-04
  • 自定义SpringBoot的白标错误页面的操作方法

    自定义SpringBoot的白标错误页面的操作方法

    Spring Boot的白标错误页面是在应用程序出现错误时(如404或500 HTTP状态码)自动生成的默认错误页面,下面小编给大家分享如何自定义SpringBoot的白标错误页面,感兴趣的朋友跟随小编一起看看吧
    2024-06-06
  • MyBatis传入List集合查询数据问题

    MyBatis传入List集合查询数据问题

    这篇文章主要介绍了MyBatis传入List集合查询数据问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • 基于maven中多个子模块的构建顺序

    基于maven中多个子模块的构建顺序

    这篇文章主要介绍了maven中多个子模块的构建顺序,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • 深度思考JDK8中日期类型该如何使用详解

    深度思考JDK8中日期类型该如何使用详解

    这篇文章主要介绍了JDK8中日期类型该如何使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06

最新评论