2025/06/02

Spring Boot 3 quartz

簡單的任務可用 spring task scheduler,複雜的要改用 quartz

pom.xml 要加上 spring-boot-starter-quartz

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

自動設定類別是 QuartzAutoConfiguration,透過註冊的 SchedulerFactoryBean 就能產生 Scheduler。參數綁定類別為 QuartzProperties,參數為 spring.quartz.*

產生 task

只要繼承 QuartzJobBean,實作 executeInternal,就可以產生 quartz 的 Job

SimpleTask.java

package com.test.quartz;

import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.springframework.scheduling.quartz.QuartzJobBean;

@Slf4j
public class SimpleTask extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext context) {
        log.info("simple task");
    }

}

設定

  • JobDetail

  • Calendar: 指定/排除特定時間

  • Trigger

TaskConfig.java

package com.test.quartz;

import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

@RequiredArgsConstructor
@Configuration
public class TaskConfig {

    public static final String SIMPLE_TASK = "simple-task";
//    private final SchedulerFactoryBean schedulerFactoryBean;
//
//    @PostConstruct
//    public void init() throws SchedulerException {
//        Scheduler scheduler = schedulerFactoryBean.getScheduler();
//        boolean exists = scheduler.checkExists(JobKey.jobKey(SIMPLE_TASK));
//        if (!exists) {
//            scheduler.scheduleJob(simpleTask(), simpleTaskTrigger());
//        }
//    }

    @Bean
    public JobDetail simpleTask() {
        return JobBuilder.newJob(SimpleTask.class)
                .withIdentity(SIMPLE_TASK)
                .withDescription("simple-task")
                .storeDurably()
                .build();
    }

    @Bean
    public Trigger simpleTaskTrigger() {
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/3 * * * * ? *");
        return TriggerBuilder.newTrigger()
                .withIdentity("simple-task-trigger")
                .forJob(simpleTask())
                .withSchedule(cronScheduleBuilder)
                .build();
    }

}

cron expression 多了最後一個 [<year>] 可以不填寫

<second> <minute> <hour> <day of month> <month> <day of week> [<year>]

ex:

"0/3 * * * * ? 2025-2030" 代表 2025-2030 每 3s 執行一次

啟動後,會看到

org.quartz.core.QuartzScheduler          : Scheduler meta-data: Quartz Scheduler (v2.3.2) 'quartzScheduler' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

設定參數

以下代表將 thread 設定為 5 個

spring:
  quartz:
    properties:
      org:
        quartz:
          threadPool:
            threadCount: 5

persistence

quartz 支援兩種 persistence 方式

  • memory

    預設。每次停止 application 就會把資料丟掉

  • JDBC

pom.xml 加上 JDBC

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>

application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/testweb
    username: root
    password: password
  quartz:
    job-store-type: jdbc
    jdbc:
      initialize-schema: always # always
    overwrite-existing-jobs: true
    properties:
      org:
        quartz:
          threadPool:
            threadCount: 5

initialize-schema 有三種

  • ALWAYS: 每一次都會重建

  • EMBEDDED: 只初始化嵌入式 DB

  • NEVER


動態維護 task

如果 task 太多,會有大量設定的 code,可改用 SchedulerFactoryBean 動態 add/remove task

package com.test.quartz;

import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

@RequiredArgsConstructor
@Configuration
public class TaskConfig {

    public static final String SIMPLE_TASK = "simple-task";
    private final SchedulerFactoryBean schedulerFactoryBean;

    @PostConstruct
    public void init() throws SchedulerException {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        boolean exists = scheduler.checkExists(JobKey.jobKey(SIMPLE_TASK));
        if (!exists) {
            scheduler.scheduleJob(simpleTask(), simpleTaskTrigger());
        }
    }

//    @Bean
    public JobDetail simpleTask() {
        return JobBuilder.newJob(SimpleTask.class)
                .withIdentity(SIMPLE_TASK)
                .withDescription("simple-task")
                .storeDurably()
                .build();
    }

//    @Bean
    public Trigger simpleTaskTrigger() {
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/3 * * * * ? *");
        return TriggerBuilder.newTrigger()
                .withIdentity("simple-task-trigger")
                .forJob(simpleTask())
                .withSchedule(cronScheduleBuilder)
                .build();
    }

}