SpringBoot3集成Quartz

来源:https://www.cnblogs.com/cicada-smile/archive/2023/08/13/17626206.html
-Advertisement-
Play Games

Quartz由Java編寫的功能豐富的開源作業調度框架,可以集成到幾乎任何Java應用程式中,並且能夠創建多個作業調度; ...


目錄

標簽:Quartz.Job.Scheduler;

一、簡介

Quartz由Java編寫的功能豐富的開源作業調度框架,可以集成到幾乎任何Java應用程式中,並且能夠創建多個作業調度;

在實際的業務中,有很多場景依賴定時任務,比如常見的:訂單超時處理,數據報表統計分析,會員等周期性管理,業務識別和預警通知等;

二、工程搭建

1、工程結構

2、依賴管理

starter-quartz組件中,實際依賴的是quartz組件2.3.2版本,使用Quartz框架時,需要自定義任務和執行邏輯,以更加靈活的方式管理業務調度;

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
    <version>${spring-boot.version}</version>
</dependency>

3、資料庫

Quartz框架使用的表結構在如圖的路徑下,本文選擇MySQL資料庫存儲,除此之外自定義兩張表:quartz_job任務表和quartz_log任務執行日誌表;

4、配置文件

在配置文件中使用Druid組件連接boot-quartz資料庫,對於Quartz框架,主要配置資料庫存儲,調度器的基礎信息,以及執行任務的線程池;

spring:
  # 定時器配置
  quartz:
    # 使用資料庫存儲
    job-store-type: jdbc
    # 初始化完成後自動啟動調度程式
    autoStartup: true
    properties:
      org:
        quartz:
          # 調度器配置
          scheduler:
            instanceName: bootQuartzScheduler
            instanceId: AUTO
          # 存儲配置
          jobStore:
            class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            tablePrefix: qrtz_
            isClustered: true
            misfireThreshold: 12000
            clusterCheckinInterval: 15000
            useProperties: false
          # 線程池配置
          threadPool:
            threadNamePrefix: Boot_Job_Pool
            threadPriority: 5
            threadCount: 10
            class: org.quartz.simpl.SimpleThreadPool

三、Quartz用法

對於任務管理的相關Web介面,採用Swagger文檔組件,介面和實體類添加註解後,訪問IP:Port/swagger-ui/index.html地址即可;

1、初始化載入

在服務啟動時執行init初始化方法,查詢quartz_job表中運行和暫停狀態的任務,判斷觸發器是否存在,如果不存在則創建,如果存在則更新;

@Service
public class QuartzJobService {

    @Resource
    private QuartzJobMapper quartzJobMapper ;
    @Resource
    private QuartzManage quartzManage;

    @PostConstruct
    public void init () {
        LambdaQueryWrapper<QuartzJob> queryWrapper = new LambdaQueryWrapper<>() ;
        queryWrapper.in(QuartzJob::getState,JobState.JOB_RUN.getStatus(),JobState.JOB_STOP.getStatus());
        List<QuartzJob> jobList = quartzJobMapper.selectList(queryWrapper);
        jobList.forEach(quartzJob -> {
            CronTrigger cronTrigger = quartzManage.getCronTrigger(quartzJob.getId()) ;
            if (Objects.isNull(cronTrigger)){
                quartzManage.createJob(quartzJob);
            } else {
                quartzManage.updateJob(quartzJob);
            }
        });
    }
}

2、新增任務

在創建任務時,需要定義JobKeyTriggerKey的構建規則,Key需要具備唯一性,通常使用任務表的主鍵ID,任務一般是基於Cron表達式被調度執行的;

@Component
public class QuartzManage {

    @Resource
    private Scheduler scheduler ;

    public void createJob (QuartzJob quartzJob){
        try {
            // 構建任務
            JobDetail jobDetail = JobBuilder.newJob(QuartzRecord.class).withIdentity(getJobKey(quartzJob.getId())).build() ;
            // 構建Cron調度器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
                                                .cronSchedule(quartzJob.getCronExpres())
                                                .withMisfireHandlingInstructionDoNothing() ;
            // 任務觸發器
            CronTrigger trigger = TriggerBuilder.newTrigger()
                                                .withIdentity(getTriggerKey(quartzJob.getId()))
                                                .withSchedule(scheduleBuilder).build() ;
            jobDetail.getJobDataMap().put(QuartzJob.JOB_PARAM_KEY,quartzJob);
            scheduler.scheduleJob(jobDetail,trigger) ;
            // 狀態校驗
            checkStop(quartzJob) ;
        } catch (SchedulerException e){
            throw new RuntimeException("createJob Fail",e) ;
        }
    }
}

3、更新任務

先通過任務ID查詢TriggerKey,對於更新來說,最常見的就是Cron表達式即調度規則的更新,或者任務的執行參數更新;

@Component
public class QuartzManage {

    @Resource
    private Scheduler scheduler ;

    public void updateJob(QuartzJob quartzJob) {
        try {
            // 查詢觸發器Key
            TriggerKey triggerKey = getTriggerKey(quartzJob.getId());
            // 構建Cron調度器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
                                                .cronSchedule(quartzJob.getCronExpres())
                                                .withMisfireHandlingInstructionDoNothing();
            // 任務觸發器
            CronTrigger trigger = getCronTrigger(quartzJob.getId())
                                .getTriggerBuilder().withIdentity(triggerKey)
                                .withSchedule(scheduleBuilder).build();
            trigger.getJobDataMap().put(QuartzJob.JOB_PARAM_KEY, quartzJob);
            scheduler.rescheduleJob(triggerKey, trigger);
            // 狀態校驗
            checkStop(quartzJob) ;
        } catch (SchedulerException e) {
            throw new RuntimeException("updateJob Fail",e) ;
        }
    }
}

4、暫停任務

先通過任務ID查詢JobKey,判斷任務是非運行狀態,則停止任務;

@Component
public class QuartzManage {

    @Resource
    private Scheduler scheduler ;

    public void checkStop (QuartzJob quartzJob){
        try {
            if(quartzJob.getState() != JobState.JOB_RUN.getStatus()){
                this.scheduler.pauseJob(getJobKey(quartzJob.getId()));
            }
        } catch (SchedulerException e){
            throw new RuntimeException("pauseJob Fail",e) ;
        }
    }

}

5、恢復任務

先通過任務ID查詢JobKey,恢復任務正常執行;

@Component
public class QuartzManage {

    @Resource
    private Scheduler scheduler ;

    public void resumeJob (Integer jobId){
        try {
            this.scheduler.resumeJob(getJobKey(jobId));
        } catch (SchedulerException e){
            throw new RuntimeException("resumeJob Fail",e) ;
        }
    }
}

6、執行一次

傳入任務主體,再通過任務ID查詢JobKey,然後立即執行一次任務;

@Component
public class QuartzManage {

    @Resource
    private Scheduler scheduler ;
    
    public void run (QuartzJob quartzJob){
        try {
            JobDataMap dataMap = new JobDataMap() ;
            dataMap.put(QuartzJob.JOB_PARAM_KEY,quartzJob);
            this.scheduler.triggerJob(getJobKey(quartzJob.getId()),dataMap);
        } catch (SchedulerException e){
            throw new RuntimeException("run Fail",e) ;
        }
    }
}

7、刪除任務

先通過任務ID查詢JobKey,徹底刪除任務;

@Component
public class QuartzManage {

    @Resource
    private Scheduler scheduler ;

    public void deleteJob (Integer jobId){
        try {
            scheduler.deleteJob(getJobKey(jobId));
        } catch (SchedulerException e){
            throw new RuntimeException("deleteJob Fail",e) ;
        }
    }
}

8、任務執行

Quartz被集成在Spring框架之後,任務類自然會以Bean對象的方式被管理,在任務創建時,設置要執行的作業類QuartzRecord,該類繼承QuartzJobBean抽象類,通過重寫executeInternal方法,來管理任務實際執行的邏輯;

public class QuartzRecord extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) {
        QuartzJob quartzJob = (QuartzJob)context.getMergedJobDataMap().get(QuartzJob.JOB_PARAM_KEY) ;
        QuartzLogService quartzLogService = (QuartzLogService)SpringContextUtil.getBean("quartzLogService") ;
        // 定時器日誌記錄
        QuartzLog quartzLog = new QuartzLog () ;
        quartzLog.setJobId(quartzJob.getId());
        quartzLog.setBeanName(quartzJob.getBeanName());
        quartzLog.setParams(quartzJob.getParams());
        quartzLog.setCreateTime(new Date());
        long beginTime = System.currentTimeMillis() ;
        try {
            // 載入並執行
            Object target = SpringContextUtil.getBean(quartzJob.getBeanName());
            Method method = target.getClass().getDeclaredMethod("run", String.class);
            method.invoke(target, quartzJob.getParams());
            long executeTime = System.currentTimeMillis() - beginTime;
            quartzLog.setTimes((int)executeTime);
            quartzLog.setState(LogState.LOG_SUS.getStatus());
        } catch (Exception e){
            // 異常信息
            long executeTime = System.currentTimeMillis() - beginTime;
            quartzLog.setTimes((int)executeTime);
            quartzLog.setState(LogState.LOG_FAIL.getStatus());
            quartzLog.setError(e.getMessage());
        } finally {
            // 保存執行日誌
            quartzLogService.insert(quartzLog) ;
        }
    }
}

四、參考源碼

文檔倉庫:
https://gitee.com/cicadasmile/butte-java-note

源碼倉庫:
https://gitee.com/cicadasmile/butte-spring-parent
Gitee主頁: https://gitee.com/cicadasmile/butte-java-note
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ![](https://img2023.cnblogs.com/blog/3076680/202308/3076680-20230812234533249-1035884507.png) # 1. 在軟體與外部環境之間的許多交匯點上,版本控制基本上處於混亂狀態 ## 1.1. 不應該為了更新自身系統 ...
  • CQRS是Command Query Responsibility Segregation的縮寫,一般稱作命令查詢職責分離。從字面意思理解,就是將命令(寫入)和查詢(讀取)的責任劃分到不同的模型中。 對比一下常用的 CRUD 模式(創建-讀取-更新-刪除),通常我們會讓用戶界面與負責所有四種操作的數... ...
  • ![](https://img2023.cnblogs.com/blog/3076680/202308/3076680-20230811224443622-1444719159.png) # 1. 部署行為是系統生命的重要組成部分 ## 1.1. 只編寫代碼是不夠的,只要沒有在生產環境中運行,一切都 ...
  • [TOC] ## 本篇概要 搭建go語言環境,除了要搭建go語言的編譯環境,還要搭建go語言的集成開發環境,為此需要選擇go語言的集成開發環境的工具,這就是“工於善其事,必先利其器”,可以大大加快自己的開發進度。 ## 集成開發環境工具(ide) 這裡我主要介紹從我2016年開始學習go語言以來使用 ...
  • ### 歡迎訪問我的GitHub > 這裡分類和彙總了欣宸的全部原創(含配套源碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 起因是懶 - 最近在開發中要用到PostgreSQL數 ...
  • Java技術體系中所提倡的自動記憶體管理最終可以歸結為自動化地解決了兩個問題:給對象分配記憶體以及回收分配給對象的記憶體。關於回收記憶體這一點,我們已經使用了大量篇幅去介紹虛擬機中的垃圾收集器體系以及運作原理,現在我們再一起來探討一下給對象分配記憶體的那點事兒。對象的記憶體分配,往大方向講,就是在堆上分配,對象... ...
  • # MyBatis--1.快速入門 ## MyBatis簡介 ### 原始jdbc操作的弊端 1. 創建、釋放頻繁導致系統資源浪費 2. sql語句在代碼中硬編碼,不易維護 3. 查詢操作時,需要手動將結果集中的數據封裝到實體中。插入操作同理需要手動 解決方案: 1. 使用資料庫連接池初始化連接資源 ...
  • ## Eureka 簡介 Eureka 是一個基於 REST 的服務發現組件,SpringCloud 將它集成在其子項目 spring-cloud-netflix 中,以實現 SpringCloud 的服務註冊與發現,同時提供了負載均衡、故障轉移等能力,目前 Eureka2.0 已經不再維護,故不推 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...