SpringBoot 整合 Quartz 定時任務框架詳解

前言

在選擇技術棧之前,一定要先明確一件事情,你真的需要用它嗎?還有其他方式可以使用嗎?

相比其他技術技術,優點在哪裡呢?使用瞭之後的利與弊等等。

寫這個主要是因為一直想寫一下定時任務這個主題,這個算是寫那篇文前期的鋪墊和基礎吧~

本文沒有聊到 Java其他的實現定時任務的方法啥的~,隻是對使用 Quartz 做瞭一個小實踐

一、簡單聊一聊 Quartz

Quartz 是一個完全由 Java 編寫的開源作業調度框架,為在 Java 應用程序中進行作業調度提供瞭簡單卻強大的機制。

Quartz 其實就是通過一個調度線程不斷的掃描數據庫中的數據來獲取到那些已經到點要觸發的任務,然後調度執行它的。這個線程就是 QuartzSchedulerThread類。其run方法中就是quartz的調度邏輯。

另外,這是一個Demo,木有考慮並發、多任務執行等等狀態的發生及處理情況,見諒。

1.1、Quartz 概念

Quartz 的幾個核心概念:

  • Job

表示一個工作,要執行的具體內容。此接口中隻有一個方法

 void execute(JobExecutionContext context) 

JobDetail 表示一個具體的可執行的調度程序,Job 是這個可執行程調度程序所要執行的內容,另外 JobDetail 還包含瞭這個任務調度的方案和策略。

Trigger 代表一個調度參數的配置,什麼時候去調。

Scheduler 代表一個調度容器,一個調度容器中可以註冊多個 JobDetail 和 Trigger。當 Trigger 與 JobDetail 組合,就可以被 Scheduler 容器調度瞭。

二、SpringBoot 使用 Quartz

2.1、基本步驟

基本步驟就那些,這篇也不是高大上講原理和流程之類的,就是偏向實操,可能一些地方在代碼中含有註釋,就不再貼說明瞭~

基本:JDK 8、SpringBoot、MybatisPlus、Quartz

創建一個 SpringBoot 項目

導入相關依賴~

 <parent>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-parent</artifactId>
     <version>2.5.2</version>
     <relativePath/>
 </parent>

 <properties>
     <java.version>1.8</java.version>
     <maven.compiler.source>8</maven.compiler.source>
     <maven.compiler.target>8</maven.compiler.target>
 </properties>

 <dependencies>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
     </dependency>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-quartz</artifactId>
     </dependency>
     <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
     </dependency>
     <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
     </dependency>
     <dependency>
         <groupId>com.baomidou</groupId>
         <artifactId>mybatis-plus-boot-starter</artifactId>
         <version>3.4.1</version>
     </dependency>

     <dependency>
         <groupId>cn.hutool</groupId>
         <artifactId>hutool-all</artifactId>
         <version>5.1.4</version>
     </dependency>
     <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>fastjson</artifactId>
         <version>1.2.76</version>
     </dependency>
 </dependencies>

項目結構:

2.2、執行 Quartz 需要的SQL文件

找到 quartz 需要的 sql 文件,在數據庫中執行,這也是Quartz持久化的基礎~

 往下滑,找到你需要的sql文件即可。

執行完的結果:

在此基礎上,我們再額外增加一張表,與我們可能有業務關聯的信息整合,這啥啥允許為空,是方便我寫測試~,並非正例

DROP TABLE IF EXISTS `sys_quartz_job`;
CREATE TABLE `sys_quartz_job`  (
  `id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `create_by` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '創建人',
  `create_time` datetime NULL DEFAULT NULL COMMENT '創建時間',
  `del_flag` int(1) UNSIGNED ZEROFILL NULL DEFAULT 0 COMMENT '刪除狀態',
  `update_by` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '修改人',
  `update_time` datetime NULL DEFAULT NULL COMMENT '修改時間',
  `job_class_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '任務類名',
  `cron_expression` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'cron表達式',
  `parameter` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '參數',
  `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
  `status` int(1) NULL DEFAULT NULL COMMENT '狀態 0正常 -1停止',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

2.3、Controller

我們直接從controller說起吧,從上往下開發。

其實一旦牽扯到表的操作,我們無疑就是crud四件事。

 /**
  * @Description: 定時任務在線管理
  * @author nzc
  */
 @RestController
 @RequestMapping("/quartzJob")
 @Slf4j
 public class QuartzJobController {

     @Autowired
     private IQuartzJobService quartzJobService;
     @Autowired
     private Scheduler scheduler;


     @RequestMapping(value = "/list", method = RequestMethod.GET)
     public Result<?> queryPageList(QuartzJob quartzJob, @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
                                    @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, HttpServletRequest req) {
         Page<QuartzJob> page = new Page<QuartzJob>(pageNo, pageSize);
         IPage<QuartzJob> pageList = quartzJobService.page(page);
         return Result.ok(pageList);

     }

     @RequestMapping(value = "/add", method = RequestMethod.POST)
     public Result<?> add(@RequestBody QuartzJob quartzJob) {
List<QuartzJob> list = quartzJobService.list(new QueryWrapper<QuartzJob>().eq("job_class_name", quartzJob.getJobClassName()));
         if (list != null && list.size() > 0) {
             return Result.error("該定時任務類名已存在");
         }
         quartzJobService.saveAndScheduleJob(quartzJob);
         return Result.ok("創建定時任務成功");
     }

     @RequestMapping(value = "/edit", method = RequestMethod.PUT)
     public Result<?> eidt(@RequestBody QuartzJob quartzJob) {
         try {
             quartzJobService.editAndScheduleJob(quartzJob);
         } catch (SchedulerException e) {
             log.error(e.getMessage(),e);
             return Result.error("更新定時任務失敗!");
         }
         return Result.ok("更新定時任務成功!");
     }

     @RequestMapping(value = "/delete", method = RequestMethod.DELETE)
     public Result<?> delete(@RequestParam(name = "id", required = true) String id) {
         QuartzJob quartzJob = quartzJobService.getById(id);
         if (quartzJob == null) {
             return Result.error("未找到對應實體");
         }
         quartzJobService.deleteAndStopJob(quartzJob);
         return Result.ok("刪除成功!");

     }

     @RequestMapping(value = "/deleteBatch", method = RequestMethod.DELETE)
     public Result<?> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
         if (ids == null || "".equals(ids.trim())) {
             return Result.error("參數不識別!");
         }
         for (String id : Arrays.asList(ids.split(","))) {
             QuartzJob job = quartzJobService.getById(id);
             quartzJobService.deleteAndStopJob(job);
         }
         return Result.ok("刪除定時任務成功!");
     }

     /**
      * 暫停定時任務
      * @param jobClassName
      */
     @GetMapping(value = "/pause")
     public Result<Object> pauseJob(@RequestParam(name = "jobClassName", required = true) String jobClassName) {
         QuartzJob job = null;
         try {
             job = quartzJobService.getOne(new LambdaQueryWrapper<QuartzJob>().eq(QuartzJob::getJobClassName, jobClassName));
             if (job == null) {
                 return Result.error("定時任務不存在!");
             }
             scheduler.pauseJob(JobKey.jobKey(jobClassName.trim()));
         } catch (SchedulerException e) {
             throw new MyException("暫停定時任務失敗");
         }
         job.setStatus(CommonConstant.STATUS_DISABLE);
         quartzJobService.updateById(job);
         return Result.ok("暫停定時任務成功");
     }

     /**
      * 恢復定時任務
      * @param jobClassName
      */
     @GetMapping(value = "/resume")
     public Result<Object> resumeJob(@RequestParam(name = "jobClassName", required = true) String jobClassName) {
         QuartzJob job = quartzJobService.getOne(new LambdaQueryWrapper<QuartzJob>().eq(QuartzJob::getJobClassName, jobClassName));
         if (job == null) {
             return Result.error("定時任務不存在!");
         }
         quartzJobService.resumeJob(job);
         //scheduler.resumeJob(JobKey.jobKey(job.getJobClassName().trim()));
         return Result.ok("恢復定時任務成功");
     }
     /** 通過id查詢*/
     @RequestMapping(value = "/queryById", method = RequestMethod.GET)
     public Result<?> queryById(@RequestParam(name = "id", required = true) String id) {
         QuartzJob quartzJob = quartzJobService.getById(id);
         return Result.ok(quartzJob);
     }
 }

2.4、Service 劃重點

 public interface IQuartzJobService extends IService<QuartzJob> {
     boolean saveAndScheduleJob(QuartzJob quartzJob);
     boolean editAndScheduleJob(QuartzJob quartzJob) throws SchedulerException;
     boolean deleteAndStopJob(QuartzJob quartzJob);
     boolean resumeJob(QuartzJob quartzJob);
 }

其中最主要的實現都是在這裡:

 @Slf4j
 @Service
 public class QuartzJobServiceImpl extends ServiceImpl<QuartzJobMapper, QuartzJob> implements IQuartzJobService {

     @Autowired
     private QuartzJobMapper quartzJobMapper;

     @Autowired
     private Scheduler scheduler;


     /**保存&啟動定時任務*/
     @Override
     public boolean saveAndScheduleJob(QuartzJob quartzJob) {
         if (CommonConstant.STATUS_NORMAL.equals(quartzJob.getStatus())) {
             // 定時器添加
             this.schedulerAdd(quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim(), quartzJob.getParameter());
         }
         // DB設置修改
         return this.save(quartzJob);
     }
     /**恢復定時任務 */
     @Override
     public boolean resumeJob(QuartzJob quartzJob) {
         schedulerDelete(quartzJob.getJobClassName().trim());
         schedulerAdd(quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim(), quartzJob.getParameter());
         quartzJob.setStatus(CommonConstant.STATUS_NORMAL);
         return this.updateById(quartzJob);
     }

     /**編輯&啟停定時任務 @throws SchedulerException */
     @Override
     public boolean editAndScheduleJob(QuartzJob quartzJob) throws SchedulerException {
         if (CommonConstant.STATUS_NORMAL.equals(quartzJob.getStatus())) {
             schedulerDelete(quartzJob.getJobClassName().trim());
             schedulerAdd(quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim(), quartzJob.getParameter());
         }else{
             scheduler.pauseJob(JobKey.jobKey(quartzJob.getJobClassName().trim()));
         }
         return this.updateById(quartzJob);
     }

     /**刪除&停止刪除定時任務*/
     @Override
     public boolean deleteAndStopJob(QuartzJob job) {
         schedulerDelete(job.getJobClassName().trim());
         return this.removeById(job.getId());
     }

     /** 添加定時任務*/
     private void schedulerAdd(String jobClassName, String cronExpression, String parameter) {
         try {
             // 啟動調度器
             scheduler.start();

             // 構建job信息
             JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass()).withIdentity(jobClassName).usingJobData("parameter", parameter).build();

             // 表達式調度構建器(即任務執行的時間)
             CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);

             // 按新的cronExpression表達式構建一個新的trigger
             CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName).withSchedule(scheduleBuilder).build();

             scheduler.scheduleJob(jobDetail, trigger);
         } catch (SchedulerException e) {
             throw new MyException("創建定時任務失敗", e);
         } catch (RuntimeException e) {
             throw new MyException(e.getMessage(), e);
         }catch (Exception e) {
             throw new MyException("後臺找不到該類名:" + jobClassName, e);
         }
     }

     /** 刪除定時任務*/
     private void schedulerDelete(String jobClassName) {
         try {
             /*使用給定的鍵暫停Trigger 。*/
             scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName));
             /*從調度程序中刪除指示的Trigger */
             scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName));
             /*從 Scheduler 中刪除已識別的Job - 以及任何關聯的Trigger */
             scheduler.deleteJob(JobKey.jobKey(jobClassName));
         } catch (Exception e) {
             log.error(e.getMessage(), e);
             throw new MyException("刪除定時任務失敗");
         }
     }
     private static Job getClass(String classname) throws Exception {
         Class<?> class1 = Class.forName(classname);
         return (Job) class1.newInstance();
     }
 }
 @Mapper
 public interface QuartzJobMapper extends BaseMapper<QuartzJob> {

 }

2.5、實體類

 @Data
 @TableName("sys_quartz_job")
 public class QuartzJob implements Serializable {

     @TableId(type = IdType.ID_WORKER_STR)
     private String id;

     private String createBy;

     private String updateBy;

     /**任務類名*/
     private String jobClassName;

     /** cron表達式 */
     private String cronExpression;
     /**  參數*/
     private String parameter;

     private String description;
     /** 狀態 0正常 -1停止*/
     private Integer status;

     @TableLogic
     private Integer delFlag;
     @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @TableField(fill = FieldFill.INSERT)
    private Date createTime;
     @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @TableField(fill = FieldFill.INSERT_UPDATE)
     private Date updateTime;
 }

另外在這裡順帶補充一下,本項目在yml中配置quartz,如下:

spring:
  ## quartz定時任務,采用數據庫方式
  quartz:
    job-store-type: jdbc

2.6、簡單的 Job 案例

如果調度器要執行任務,首先得要有一個任務相關滴類。

寫瞭兩個平常的案例,一個是不帶參數的,一個是帶參數的。

/**
 * 不帶參的簡單定時任務
 * @Author nzc
 */
@Slf4j
public class SampleJob implements Job {

	@Override
	public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {

		log.info(String.format("Ning zaichun的 普通定時任務 SampleJob !  時間:" + new Date()));
	}
}

帶參數的:

 /**
  * 帶參數的簡單的定時任務
  * @Author nzc
  */
 @Slf4j
 public class SampleParamJob implements Job {

     /**
      * 若參數變量名修改 QuartzJobController中也需對應修改
      */
     private String parameter;

     public void setParameter(String parameter) {
         this.parameter = parameter;
     }
     @Override
     public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
         log.info(String.format("welcome %s! Jeecg-Boot 帶參數定時任務 SampleParamJob !   時間:" + LocalDateTime.now(), this.parameter));
     }
 }

2.7、那麼該如何使用呢?

啟動項目,讓我們拿起來postman來測試吧,康康該如何使用,數據表在使用的時候,又有怎麼樣的變化~

我們直接來測試添加定時任務的接口,先來個不用參數的吧

 {
     "createBy": "nzc",
     "jobClassName": "com.nzc.quartz.job.SampleJob",
     "cronExpression": "0/10 * * * * ? ",
     "description": "每十秒執行一次",
     "status": "0"
 }

添加完之後就已經在執行瞭。 

此時我們將項目停止,重新啟動~

調度器也會主動去檢測任務信息,如果有,就會開始執行。

我們再來測測帶有參數的 

也是可以成功的,並且也是按照我們設定的時間執行的

其他的接口也是同樣如此,根據接口的設定,將參數傳入即可

到此這篇關於SpringBoot 整合 Quartz 定時任務框架詳解的文章就介紹到這瞭,更多相關SpringBoot 整合 Quartz 內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: