1. 程式人生 > >SpringBoot整合任務排程框架Quartz及持久化配置

SpringBoot整合任務排程框架Quartz及持久化配置

[toc] > 本文側重SpringBoot與Quartz的整合,Quartz的基本入門概念不清楚的小夥伴可以看看這篇文章:[任務排程框架Quartz快速入門!](https://www.cnblogs.com/summerday152/p/14192845.html) ## 本篇要點 - 介紹SpringBoot與Quartz單機版整合。 - 介紹Quartz持久化儲存。 ## SpringBoot與Quartz單機版快速整合 學習完非Spring環境下Quartz的使用,再來看SpringBoot你會感到更加自如,因為SpringBoot無非是利用它自動配置的特性,將一些重要的Bean自動配置到環境中,我們直接開箱即用,關於Quartz的自動配置定義在QuartzAutoConfiguration中。 ### 引入依賴 主要是`spring-boot-starter-quartz`這個依賴,是SpringBoot與Quartz的整合。 ```xml org.springframework.boot
spring-boot-starter-web
org.springframework.boot spring-boot-starter-quartz ``` ### 建立Job 為了演示兩種Trigger及兩種配置方式,我們建立兩個不同的Job。 ```java @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方法。 ```java 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 ```java @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 ```java @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配置 ```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`中。 ### 主啟動類 ```java @SpringBootApplication public class DemoSpringBootApplication { public static void main(String[] args) { SpringApplication.run(DemoSpringBootApplication.class, args); } } ``` ### 測試 啟動程式,FirstJob每5s執行一次,SecondJob每10s執行一次。 ```java [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中有支援許多種資料庫的指令碼,可以按需執行。 ```sql 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相關依賴 ```xml org.springframework.boot
spring-boot-starter-web
org.springframework.boot spring-boot-starter-quartz mysql mysql-connector-java org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-jdbc
``` ### 配置yml ```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 # 執行緒池型別 ``` ### 配置資料來源 ```java @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); } } ``` ### 建立任務 ```java @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 set = new HashSet<>(); set.add(trigger); // boolean replace 表示啟動時對資料庫中的quartz的任務進行覆蓋。 scheduler.scheduleJob(jobDetail, set, true); } } ``` ### 啟動測試 啟動測試之後,我們的quartz任務相關資訊就已經成功儲存到mysql中了。 ```sql 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](https://gitee.com/tqbx/springboot-samples-learn),另有其他SpringBoot的整合哦。 ## 參考閱讀 - [【Quartz】Quartz儲存與持久化-基於quartz.properties的配置](http://blog.csdn.net/evankaka) - [芋道 Spring Boot 定時任務入門](http://www.iocoder.cn/Spring-Boot/Job/) - https://www.jianshu.com/p/7663f0ed486a - [WIKIPEDIA : Quartz(scheduler)](https://en.wikipedia.org/wiki/Quartz_(scheduler)) - http://www.quartz-scheduler.org/documentation/quartz-2.2.2/tutorials/ - [Quartz資料庫表分析](https://blog.csdn.net/xiaoniu_888/article/details/8