Quartz整合到SpringBoot(持久化到数据库)
背景
最近完成了一个小的后台管理系统的权限部分,想着要扩充点东西,并且刚好就完成了一个自动疫情填报系统,但是使用的定时任务是静态的,非常不利于扩展和调控,就想到了用Quartz实现。
Quartz简介
Quartz
是一个优秀的任务调度框架,有静态任务调度和动态任务调度两种类型。静态任务调度是编写好代码,然后在启动的时候,将任务保存在内内存中,也就是下面的RAMJobStore
,实现相对简单;动态任务调度主要是持久化到数据库,即下面的JDBC作业存储
,实现方式相对复杂一点,但是可操作性更强。
- RAMJobStore :RAM也就是内存,默认情况下Quartz会将任务调度存在内存中,这种方式性能是最好的,因为内存的速度是最快的。不好的地方就是数据缺乏持久性,但程序崩溃或者重新发布的时候,所有运行信息都会丢失
- JDBC作业存储:存到数据库之后,可以做单点也可以做集群,当任务多了之后,可以统一进行管理。关闭或者重启服务器,运行的信息都不会丢失。缺点就是运行速度快慢取决于连接数据库的快慢。
Quartz相关概念
-
Scheduler:调度器,进行任务调度;quartz的大脑
-
Job:业务job,亦可称业务组件;定时任务的具体执行业务需要实现此接口,调度器会调用此接口的execute方法完成我们的定时业务
-
JobDetail:用来定义业务Job的实例,我们可以称之为quartz job,很多时候我们谈到的job指的是JobDetail
-
Trigger:触发器,用来定义一个指定的Job何时被执行
-
JobBuilder:Job构建器,用来定义或创建JobDetail的实例;JobDetail限定了只能是Job的实例
-
TriggerBuilder:触发器构建器,用来定义或创建触发器的实例
RAMJobStore的实现
整体流程
-
首先需要创建任务(Job),比如取消订单、定时发送短信邮件之类的,这是我们的任务主体,也是写业务逻辑的地方。
-
创建任务调度器(Scheduler),这是用来调度任务的,主要用于启动、停止、暂停、恢复等操作,也就是那几个api的用法。
-
创建任务明细(JobDetail),最开始我们编写好任务(Job)后,只是写好业务代码,并没有触发,这里需要用JobDetail来和之前创建的任务(Job)关联起来,便于执行。
-
创建触发器(Trigger),触发器是来定义任务的规则的,比如几点执行,几点结束,几分钟执行一次等等。这里触发器主要有两大类(SimpleTrigger和CronTrigger)。
-
根据Scheduler来启动JobDetail与Trigger
导入依赖
<!--quartz定时调度依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
创建Job
要求: 需实现Job接口,并且这个接口就一个execute()方法需要重写,方法内容就是具体的业务逻辑。如果是动态任务呢,比如取消订单,每次执行都是不同的订单号。这个时候就需要在创建任务(JobDetail)或者创建触发器(Trigger)的那里传入参数,然后在这里通过JobExecutionContext来获取参数进行处理,
/**
* @author Mr.zhou
* @version 1.0
* @date 2020/11/17 17:52
*/
public class TestJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.err.println("Hello quartz, " + jobExecutionContext.getJobDetail().getJobDataMap().getString("message"));
System.err.println("Hello quartz, " + jobExecutionContext.getMergedJobDataMap().getString("work"));
System.err.println();
}
}
创建任务调度器
这里我们用的是SpringBoot,SpringBoot会自动帮我们装配任务调度器,在使用的时候,直接注入即可。
@Resource
private Scheduler scheduler;
如果没用使用SpringBoot,那么就需要手动创建了。通常优秀的框架,创建某些核心类都会使用工厂模式或者构建者模式,值得学习。
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
创建任务明细:JobDetail
// 创建任务详情
JobDetail jobDetail = JobBuilder.newJob(TestJob.class)
// 添加参数
.usingJobData("message", "米西米西滑不拉几")
// 添加认证信息,共有三种认证方式
.withIdentity(orderNum)
.build();
创建触发器:Trigger
SimpleSchedule策略
// 创建触发器
Trigger trigger = TriggerBuilder.newTrigger()
// 添加参数
.usingJobData("work", "我在写代码,你呢?")
// 认证信息
.withIdentity(orderNum)
// 开始时间
.startAt(start)
// 执行策率,有SimpleSchedule和CronSchedule两种
.withSchedule(
SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5)
.repeatForever())
.build();
CronSchedule策略
// 创建触发器
Trigger trigger = TriggerBuilder.newTrigger()
// 添加参数
.usingJobData("work", "我在写代码,你呢?")
// 认证信息
.withIdentity(orderNum)
// 开始时间
.startNow()
// 执行策率,有SimpleSchedule和CronSchedule两种
.withSchedule(CronScheduleBuilder.cronSchedule("*/5 * * * * *"))
.build();
注意:.startNow()
和.startAt()
这里有个坑,这两个方法是对同一个成员变量进行修改的 也就是说startAt和startNow同时调用的时候任务开始的时间是按后面调用的方法为主的,谁写在后面用谁。看看这里的源码:
启动任务
上面都已经将工作完成了,接下来就是启动任务了
// 启动定时任务
scheduler.scheduleJob(jobDetail, trigger);
if (!scheduler.isShutdown()) {
scheduler.start();
}
实现任务的常规操作
上面已经介绍了怎么去定义一个任务,并成功启动,下面就简单介绍任务的启动、暂停、恢复、以及删除等操作。
@RestController
@RequestMapping("/quartz")
public class QuartzController {
@Resource
private Scheduler scheduler;
/**
* 启动一个定时任务
* @param orderNum 编号
* @return 结果
* @throws SchedulerException 异常
*/
@PostMapping("/start")
public R quartz(@RequestParam("orderNum") String orderNum) throws SchedulerException {
// 当前时间10秒之后
Date start = new Date(System.currentTimeMillis() + 10 * 1000);
// 创建任务详情
JobDetail jobDetail = JobBuilder.newJob(TestJob.class)
// 添加参数
.usingJobData("message", "米西米西滑不拉几")
// 添加认证信息,共有三种认证方式
.withIdentity(orderNum)
.build();
// 创建触发器
Trigger trigger = TriggerBuilder.newTrigger()
// 添加参数
.usingJobData("work", "我在写代码,你呢?")
// 认证信息
.withIdentity(orderNum)
// 开始时间
.startAt(start)
// 执行策率,有SimpleSchedule和CronSchedule两种
.withSchedule(CronScheduleBuilder.cronSchedule("*/5 * * * * ?"))
.build();
// 启动定时任务
scheduler.scheduleJob(jobDetail, trigger);
if (!scheduler.isShutdown()) {
scheduler.start();
}
System.err.println("定时任务启动成功");
return R.ok();
}
/**
* 任务关闭
* @param oderNum 标识
* @return 结果
* @throws SchedulerException 异常
*/
@PostMapping("/shutdown")
public R shutdown(@RequestParam("orderNum") String oderNum) throws SchedulerException {
scheduler.pauseTrigger(TriggerKey.triggerKey(oderNum));
return R.ok();
}
/**
* 任务恢复
* @param oderNum 标识
* @return 结果
* @throws SchedulerException 异常
*/
@PostMapping("/resume")
public R resume(@RequestParam("orderNum") String oderNum) throws SchedulerException {
scheduler.resumeTrigger(TriggerKey.triggerKey(oderNum));
return R.ok();
}
/**
* 任务删除
* @param oderNum 标识
* @return 结果
* @throws SchedulerException 异常
*/
@PostMapping("/del")
public R del(@RequestParam("/oderNum") String oderNum) throws SchedulerException {
scheduler.pauseTrigger(TriggerKey.triggerKey(oderNum));
scheduler.unscheduleJob(TriggerKey.triggerKey(oderNum));
scheduler.deleteJob(JobKey.jobKey(oderNum));
return R.ok();
}
}
结果示例
由于我项目已经集成了Swagger文档,所以我就直接在这上面进行接口测试了
-
开启任务
-
任务启动之后的输出效果
到这里,应该就清楚了Quartz和SpringBoot整合,大致流程,了解了RAMJobStore如何实现,那么数据库持久化的实现也应该有了基本思路了。
JDBC作业存储实现
大致流程
- 创建SpringBoot工程,并且加入Web和Quartz依赖
- 下载Quartz官方提供的数据库脚本,注意版本,并将其导入到自己项目的数据库中
- 添加Quartz配置