SpringBoot整合任務調度框架Quartz及持久化配置

本文側重SpringBoot與Quartz的整合,Quartz的基本入門概念不清楚的小夥伴可以看看這篇文章:任務調度框架Quartz快速入門!

本篇要點

  • 介紹SpringBoot與Quartz單機版整合。
  • 介紹Quartz持久化存儲。

SpringBoot與Quartz單機版快速整合

學習完非Spring環境下Quartz的使用,再來看SpringBoot你會感到更加自如,因爲SpringBoot無非是利用它自動配置的特性,將一些重要的Bean自動配置到環境中,我們直接開箱即用,關於Quartz的自動配置定義在QuartzAutoConfiguration中。

引入依賴

主要是spring-boot-starter-quartz這個依賴,是SpringBoot與Quartz的整合。

        <!-- 實現對 Spring MVC 的自動化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 實現對 Quartz 的自動化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

創建Job

爲了演示兩種Trigger及兩種配置方式,我們創建兩個不同的Job。

@Slf4j
public class FirstJob extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        String now = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now());
        log.info("當前的時間: " + now);
    }
}

@Slf4j
public class SecondJob extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        String now = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now());
        log.info("SecondJob執行, 當前的時間: " + now);
    }
}

我們在創建Job的時候,可以實現Job接口,也可以繼承QuartzJobBean。

QuartzJobBean實現了Job,並且定義了公用的execute方法,子類可以繼承QuartzJobBean並實現executeInternal方法。

public abstract class QuartzJobBean implements Job {

	/**
	 * This implementation applies the passed-in job data map as bean property
	 * values, and delegates to {@code executeInternal} afterwards.
	 * @see #executeInternal
	 */
	@Override
	public final void execute(JobExecutionContext context) throws JobExecutionException {
		try {
             // 將當前對象包裝爲BeanWrapper
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            // 設置屬性
			MutablePropertyValues pvs = new MutablePropertyValues();
			pvs.addPropertyValues(context.getScheduler().getContext());
			pvs.addPropertyValues(context.getMergedJobDataMap());
			bw.setPropertyValues(pvs, true);
		}
		catch (SchedulerException ex) {
			throw new JobExecutionException(ex);
		}
        // 子類實現該方法
		executeInternal(context);
	}

	/**
	 * Execute the actual job. The job data map will already have been
	 * applied as bean property values by execute. The contract is
	 * exactly the same as for the standard Quartz execute method.
	 * @see #execute
	 */
	protected abstract void executeInternal(JobExecutionContext context) throws JobExecutionException;

}

調度器Scheduler綁定

Scheduler綁定有兩種方式,一種是使用bena的自動配置,一種是Scheduler手動配置。

自動配置,這裏演示SimpleScheduleBuilder

@Configuration
public class QuartzConfig {

    private static final String ID = "SUMMERDAY";

    @Bean
    public JobDetail jobDetail1() {
        return JobBuilder.newJob(FirstJob.class)
                .withIdentity(ID + " 01")
                .storeDurably()
                .build();
    }

    @Bean
    public Trigger trigger1() {
        // 簡單的調度計劃的構造器
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(5) // 頻率
                .repeatForever(); // 次數

        return TriggerBuilder.newTrigger()
                .forJob(jobDetail1())
                .withIdentity(ID + " 01Trigger")
                .withSchedule(scheduleBuilder)
                .build();
    }
}

手動配置,這裏演示CronScheduleBuilder

@Component
public class JobInit implements ApplicationRunner {

    private static final String ID = "SUMMERDAY";

    @Autowired
    private Scheduler scheduler;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        JobDetail jobDetail = JobBuilder.newJob(FirstJob.class)
                .withIdentity(ID + " 01")
                .storeDurably()
                .build();
        CronScheduleBuilder scheduleBuilder =
                CronScheduleBuilder.cronSchedule("0/5 * * * * ? *");
        // 創建任務觸發器
        Trigger trigger = TriggerBuilder.newTrigger()
                .forJob(jobDetail)
                .withIdentity(ID + " 01Trigger")
                .withSchedule(scheduleBuilder)
                .startNow() //立即執行一次任務
                .build();
        // 手動將觸發器與任務綁定到調度器內
        scheduler.scheduleJob(jobDetail, trigger);
    }
}

yml配置

spring:
  # Quartz 的配置,對應 QuartzProperties 配置類
  quartz:
    job-store-type: memory # Job 存儲器類型。默認爲 memory 表示內存,可選 jdbc 使用數據庫。
    auto-startup: true # Quartz 是否自動啓動
    startup-delay: 0 # 延遲 N 秒啓動
    wait-for-jobs-to-complete-on-shutdown: true # 應用關閉時,是否等待定時任務執行完成。默認爲 false ,建議設置爲 true
    overwrite-existing-jobs: false # 是否覆蓋已有 Job 的配置
    properties: # 添加 Quartz Scheduler 附加屬性
      org:
        quartz:
          threadPool:
            threadCount: 25 # 線程池大小。默認爲 10 。
            threadPriority: 5 # 線程優先級
            class: org.quartz.simpl.SimpleThreadPool # 線程池類型
#    jdbc: # 這裏暫時不說明,使用 JDBC 的 JobStore 的時候,才需要配置

SpringBoot自動配置中:spring.quartz對應的配置項定義在QuartzProperties中。

主啓動類

@SpringBootApplication
public class DemoSpringBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoSpringBootApplication.class, args);
    }
}

測試

啓動程序,FirstJob每5s執行一次,SecondJob每10s執行一次。

 [eduler_Worker-1] com.hyhwky.standalone.FirstJob           : FirstJob執行, 當前的時間: 2020-12-26 16:54:00
 [eduler_Worker-2] com.hyhwky.standalone.SecondJob          : SecondJob執行, 當前的時間: 2020-12-26 16:54:00
 [eduler_Worker-3] com.hyhwky.standalone.FirstJob           : FirstJob執行, 當前的時間: 2020-12-26 16:54:05
 [eduler_Worker-4] com.hyhwky.standalone.SecondJob          : SecondJob執行, 當前的時間: 2020-12-26 16:54:10
 [eduler_Worker-5] com.hyhwky.standalone.FirstJob           : FirstJob執行, 當前的時間: 2020-12-26 16:54:10
 [eduler_Worker-6] com.hyhwky.standalone.FirstJob           : FirstJob執行, 當前的時間: 2020-12-26 16:54:15
 [eduler_Worker-7] com.hyhwky.standalone.SecondJob          : SecondJob執行, 當前的時間: 2020-12-26 16:54:20

Quartz持久化配置

Quartz持久化配置提供了兩種存儲器:

類型 優點 缺點
RAMJobStore 不要外部數據庫,配置容易,運行速度快 因爲調度程序信息是存儲在被分配給 JVM 的內存裏面,所以,當應用程序停止運行時,所有調度信息將被丟失。另外因爲存儲到JVM內存裏面,所以可以存儲多少個 Job 和 Trigger 將會受到限制
JDBC 作業存儲 支持集羣,因爲所有的任務信息都會保存到數據庫中,可以控制事物,還有就是如果應用服務器關閉或者重啓,任務信息都不會丟失,並且可以恢復因服務器關閉或者重啓而導致執行失敗的任務 運行速度的快慢取決與連接數據庫的快慢

創建數據庫表

爲了測試Quartz的持久化配置,我們事先在mysql中創建一個數據庫quartz,並執行腳本,腳本藏在org\quartz-scheduler\quartz\2.3.2\quartz-2.3.2.jar!\org\quartz\impl\jdbcjobstore\tables_mysql_innodb.sql,jdbcjobstore中有支持許多種數據庫的腳本,可以按需執行。

mysql> use quartz;
Database changed
mysql> show tables;
+--------------------------+
| Tables_in_quartz         |
+--------------------------+
| qrtz_blob_triggers       |## blog類型存儲triggers
| qrtz_calendars           |## 以blog類型存儲Calendar信息
| qrtz_cron_triggers       |## 存儲cron trigger信息
| qrtz_fired_triggers      |## 存儲已觸發的trigger相關信息
| qrtz_job_details         |## 存儲每一個已配置的job details
| qrtz_locks               |## 存儲悲觀鎖的信息
| qrtz_paused_trigger_grps |## 存儲已暫停的trigger組信息
| qrtz_scheduler_state     |## 存儲Scheduler狀態信息
| qrtz_simple_triggers     |## 存儲simple trigger信息
| qrtz_simprop_triggers    |## 存儲其他幾種trigger信息
| qrtz_triggers            |## 存儲已配置的trigger信息
+--------------------------+

所有的表中都含有一個SCHED_NAME字段,對應我們配置的scheduler-name,相同 Scheduler-name的節點,形成一個 Quartz 集羣。

引入mysql相關依賴

    <dependencies>
        <!-- 實現對 Spring MVC 的自動化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 實現對 Quartz 的自動化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
    </dependencies>

配置yml

spring:
  datasource:
    quartz:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/quartz?serverTimezone=GMT%2B8
      username: root
      password: 123456
  quartz:
    job-store-type: jdbc # 使用數據庫存儲
    scheduler-name: hyhScheduler # 相同 Scheduler 名字的節點,形成一個 Quartz 集羣
    wait-for-jobs-to-complete-on-shutdown: true # 應用關閉時,是否等待定時任務執行完成。默認爲 false ,建議設置爲 true
    jdbc:
      initialize-schema: never # 是否自動使用 SQL 初始化 Quartz 表結構。這裏設置成 never ,我們手動創建表結構。
    properties:
      org:
        quartz:
          # JobStore 相關配置
          jobStore:
            dataSource: quartzDataSource # 使用的數據源
            class: org.quartz.impl.jdbcjobstore.JobStoreTX # JobStore 實現類
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            tablePrefix: QRTZ_ # Quartz 表前綴
            isClustered: true # 是集羣模式
            clusterCheckinInterval: 1000
            useProperties: false
          # 線程池相關配置
          threadPool:
            threadCount: 25 # 線程池大小。默認爲 10 。
            threadPriority: 5 # 線程優先級
            class: org.quartz.simpl.SimpleThreadPool # 線程池類型

配置數據源

@Configuration
public class DataSourceConfiguration {

    private static HikariDataSource createHikariDataSource(DataSourceProperties properties) {
        // 創建 HikariDataSource 對象
        HikariDataSource dataSource = properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
        // 設置線程池名
        if (StringUtils.hasText(properties.getName())) {
            dataSource.setPoolName(properties.getName());
        }
        return dataSource;
    }

    /**
     * 創建 quartz 數據源的配置對象
     */
    @Primary
    @Bean(name = "quartzDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.quartz")
    // 讀取 spring.datasource.quartz 配置到 DataSourceProperties 對象
    public DataSourceProperties quartzDataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * 創建 quartz 數據源
     */
    @Bean(name = "quartzDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.quartz.hikari")
    @QuartzDataSource
    public DataSource quartzDataSource() {
        // 獲得 DataSourceProperties 對象
        DataSourceProperties properties = this.quartzDataSourceProperties();
        // 創建 HikariDataSource 對象
        return createHikariDataSource(properties);
    }

}

創建任務

@Component
public class JobInit implements ApplicationRunner {

    private static final String ID = "SUMMERDAY";

    @Autowired
    private Scheduler scheduler;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        JobDetail jobDetail = JobBuilder.newJob(SecondJob.class)
                .withIdentity(ID + " 02")
                .storeDurably()
                .build();
        CronScheduleBuilder scheduleBuilder =
                CronScheduleBuilder.cronSchedule("0/10 * * * * ? *");
        // 創建任務觸發器
        Trigger trigger = TriggerBuilder.newTrigger()
                .forJob(jobDetail)
                .withIdentity(ID + " 02Trigger")
                .withSchedule(scheduleBuilder)
                .startNow() //立即執行一次任務
                .build();
        Set<Trigger> set = new HashSet<>();
        set.add(trigger);
        // boolean replace 表示啓動時對數據庫中的quartz的任務進行覆蓋。
        scheduler.scheduleJob(jobDetail, set, true);
    }
}

啓動測試

啓動測試之後,我們的quartz任務相關信息就已經成功存儲到mysql中了。

mysql> select * from qrtz_simple_triggers;
+--------------+---------------------+---------------+--------------+-----------------+-----------------+
| SCHED_NAME   | TRIGGER_NAME        | TRIGGER_GROUP | REPEAT_COUNT | REPEAT_INTERVAL | TIMES_TRIGGERED |
+--------------+---------------------+---------------+--------------+-----------------+-----------------+
| hyhScheduler | SUMMERDAY 01Trigger | DEFAULT       |           -1 |            5000 |             812 |
+--------------+---------------------+---------------+--------------+-----------------+-----------------+
1 row in set (0.00 sec)

mysql> select * from qrtz_cron_triggers;
+--------------+---------------------+---------------+------------------+---------------+
| SCHED_NAME   | TRIGGER_NAME        | TRIGGER_GROUP | CRON_EXPRESSION  | TIME_ZONE_ID  |
+--------------+---------------------+---------------+------------------+---------------+
| hyhScheduler | SUMMERDAY 02Trigger | DEFAULT       | 0/10 * * * * ? * | Asia/Shanghai |
+--------------+---------------------+---------------+------------------+---------------+

源碼下載

本文內容均爲對優秀博客及官方文檔總結而得,原文地址均已在文中參考閱讀處標註。最後,文中的代碼樣例已經全部上傳至Gitee:https://gitee.com/tqbx/springboot-samples-learn,另有其他SpringBoot的整合哦。

參考閱讀

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章