一、前言
Quartz是一个非常好的定时任务框架,它的轻量级、高可靠性、易于使用的特点,使得它成为一个非常受欢迎的任务框架。这篇文章中我们将介绍如何使用SpringBoot整合Quartz并将定时任务写入数据库中,并对定时任务进行如启动、删除、暂停、恢复等操作。
二、整合Quartz
1.添加相关依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>HelloDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>HelloDemo</name>
<description>HelloDemo</description>
<properties>
<java.version>8</java.version>
</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-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder-jammy-base:latest</builder>
</image>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.编写相关配置
我们在application.properties文件中进行数据库的配置和Quartz的配置。
#数据库配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/shop?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true
#quartz配置
# 数据存储方式 数据库的方式
spring.quartz.job-store-type=jdbc
# 调度标识名
spring.quartz.properties.org.quartz.scheduler.instanceName=clusteredScheduler
# ID设置为自动获取
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
#数据保存方式为持久化
spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
#数据库平台
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#表的前缀
spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_
#是否加入集群
spring.quartz.properties.org.quartz.jobStore.isClustered=false
#调度实例失效的检查时间间隔
spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=10000
#设置为True不会出现序列化非字符串到BLOB时产生的类版本问题
spring.quartz.properties.org.quartz.jobStore.useProperties=false
#ThreadPool实现的类名
spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
#线程数量
spring.quartz.properties.org.quartz.threadPool.threadCount=5
#线程优先级
spring.quartz.properties.org.quartz.threadPool.threadPriority=5
#自创建父线程
spring.quartz.properties.org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
3.添加Quartz表
我们在使用quartz做持久化的时候需要用到quartz的11张表,我们可以去quartz的官网下载对应版本的quartz.地址:http://www.quartz-scheduler.org/downloads/
下载之后解压找到对应版本的sql文件,并选择innodb引擎的版本。
我们导入这个文件之后,刷新数据库,发现新增了Quartz相关的数据库表。
具体每张表的说明:
1.qrtz_blob_triggers:Trigger 作为 Blob 类型存储(用于 Quartz 用户用 JDBC创建他们自己定制的 Trigger 类型,JobStore 并不知道如何存储实例的时候)
2.qrtz_calendars:以 Blob 类型存储 Quartz 的 Calendar 信息
3.qrtz_cron_triggers:存储 Cron Trigger,包括 Cron表达式和时区信息
4.qrtz_fired_triggers:存储与已触发的 Trigger 相关的状态信息,以及相联 Job的执行信息
5.qrtz_job_details:存储每一个已配置的Job的详细信息
6.qrtz_locks:存储程序的悲观锁的信息(假如使用了悲观锁)
7,qrtz_paused_trigger_grps:存储已暂停的 Trigger 组的信息
8.qrtz_scheduler_state:存储少量的有关 Scheduler 的状态信息,和别的 Scheduler实例(假如是用于一个集群中)
9.qrtz_simple_triggers:存储简单的Trigger,包括重复次数,间隔,以及已触的次数
10.qrtz_simprop_triggers:存储CalendarIntervalTrigger和DailyTimeIntervalTrigger两种类型的触发器。
11.qrtz_triggers:存储已配置的 Trigger 的信息
4.创建相关实体类
package com.example.hellodemo.bean;
import lombok.Data;
import java.io.Serializable;
/**
* @author qx
* @date 2023/11/27
* @des 定时内容实体
*/
@Data
public class JobDetailModel implements Serializable {
private String content;
private Integer type;
}
package com.example.hellodemo.bean;
import lombok.Data;
import java.io.Serializable;
/**
* @author qx
* @date 2023/11/27
* @des 操作类型实体
*/
@Data
public class OperationModel implements Serializable {
private String jobName;
private String jobGroup;
}
package com.example.hellodemo.bean;
import lombok.Data;
import java.io.Serializable;
/**
* @author qx
* @date 2023/11/27
* @des 定时任务实体类
*/
@Data
public class QuartzModel implements Serializable {
private String cron;
private String jobGroup;
private String jobName;
private String triggerGroup;
private String triggerName;
private JobDetailModel jobData;
}
5.实现定时任务执行器
package com.example.hellodemo.task;
import com.example.hellodemo.bean.JobDetailModel;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.util.Date;
/**
* @author qx
* @date 2023/11/24
* @des 定时任务执行器
*/
@Slf4j
public class SimpleTask extends QuartzJobBean {
/**
* 根据不同的jobDetail携带的type参数区分不同的业务
*/
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
synchronized (this) {
log.info("开始执行定时任务...........");
JobDetailModel jobDetailModel = (JobDetailModel) context.getJobDetail().getJobDataMap().get("jobDetailModel");
Integer type = jobDetailModel.getType();
String content = jobDetailModel.getContent();
try {
switch (type) {
case 1:
log.info("task1被执行,content:{}", content);
Thread.sleep(5000);
log.info("-------task1定时任务执行结束------");
break;
case 2:
log.info("task2被执行,content:{}", content);
Thread.sleep(5000);
log.info("-------task2定时任务执行结束------");
break;
default:
log.info("-------定时任务执行结束------");
log.info("-------定时任务执行结束------");
}
} catch (Throwable t) {
log.error(t.getMessage(), t);
}
log.info("定时任务执行结束..............");
}
}
}
5.创建服务层及其实现类
服务层QuartzService
package com.example.hellodemo.service;
import com.example.hellodemo.bean.OperationModel;
import com.example.hellodemo.bean.QuartzModel;
import org.quartz.SchedulerException;
import java.util.List;
import java.util.Map;
/**
* @author qx
* @date 2023/11/27
* @des Quartz服务层
*/
public interface QuartzService {
/**
* 新增定时任务
*/
Boolean addJob(QuartzModel quartzModel) throws SchedulerException;
/**
* 暂停定时任务
*/
Boolean pauseJob(OperationModel operationModel) throws SchedulerException;
/**
* 继续定时任务
*/
Boolean resumeJob(OperationModel operationModel) throws SchedulerException;
/**
* 删除定时任务
*/
Boolean deleteJob(OperationModel operationModel) throws SchedulerException;
/**
* 查询所有计划中的任务列表
*/
List<Map<String, Object>> queryAllJob() throws SchedulerException;
/**
* 查询正在运行的任务
*/
List<Map<String, Object>> queryAllRunningJob() throws SchedulerException;
}
服务层实现类QuartzServiceImpl
package com.example.hellodemo.service.impl;
import com.example.hellodemo.bean.JobDetailModel;
import com.example.hellodemo.bean.OperationModel;
import com.example.hellodemo.bean.QuartzModel;
import com.example.hellodemo.service.QuartzService;
import com.example.hellodemo.task.SimpleTask;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
/**
* @author qx
* @date 2023/11/27
* @des Quartz服务层实现类
*/
@Slf4j
@Service
public class QuartzServiceImpl implements QuartzService {
@Autowired
private Scheduler scheduler;
@Override
public Boolean addJob(QuartzModel quartzModel) throws SchedulerException {
log.info("quartzModel:{}", quartzModel);
String cron = quartzModel.getCron();
JobDetail jobDetail = JobBuilder.newJob(SimpleTask.class).withIdentity(quartzModel.getJobName(), quartzModel.getJobGroup()).build();
JobDetailModel jobDetailModel = quartzModel.getJobData();
if (!Objects.isNull(jobDetailModel)) {
jobDetail.getJobDataMap().put("jobDetailModel", jobDetailModel);
}
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(quartzModel.getTriggerName(), quartzModel.getTriggerGroup()).startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.standby();
log.info("----定时任务成功添加进quartz队列中----");
return true;
}
@Override
public Boolean pauseJob(OperationModel operationModel) throws SchedulerException {
scheduler.pauseJob(JobKey.jobKey(operationModel.getJobName(), operationModel.getJobGroup()));
log.info("暂停定时任务成功");
return true;
}
@Override
public Boolean resumeJob(OperationModel operationModel) throws SchedulerException {
scheduler.resumeJob(JobKey.jobKey(operationModel.getJobName(), operationModel.getJobGroup()));
log.info("恢复定时任务成功");
return true;
}
@Override
public Boolean deleteJob(OperationModel operationModel) throws SchedulerException {
TriggerKey triggerKey = TriggerKey.triggerKey(operationModel.getJobName(), operationModel.getJobGroup());
// 停止触发器
scheduler.resumeTrigger(triggerKey);
// 移除触发器
scheduler.unscheduleJob(triggerKey);
scheduler.deleteJob(JobKey.jobKey(operationModel.getJobName(), operationModel.getJobGroup()));
log.info("删除定时任务成功");
return true;
}
@Override
public List<Map<String, Object>> queryAllJob() throws SchedulerException {
List<Map<String, Object>> jobList = new ArrayList<>();
GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
for (JobKey jobKey : jobKeys) {
List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
for (Trigger trigger : triggers) {
jobList.add(this.buildMap(jobKey, trigger));
}
}
return jobList;
}
@Override
public List<Map<String, Object>> queryAllRunningJob() throws SchedulerException {
List<Map<String, Object>> jobList = new ArrayList<>();
List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
for (JobExecutionContext executingJob : executingJobs) {
JobDetail jobDetail = executingJob.getJobDetail();
JobKey jobKey = jobDetail.getKey();
Trigger trigger = executingJob.getTrigger();
jobList.add(this.buildMap(jobKey, trigger));
}
return jobList;
}
/**
* 返回指定的map集合
*/
private Map<String, Object> buildMap(JobKey jobKey, Trigger trigger) throws SchedulerException {
Map<String, Object> map = new HashMap<>();
map.put("jobName", jobKey.getName());
map.put("jobGroupName", jobKey.getGroup());
map.put("description", "触发器:" + trigger.getKey());
Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
map.put("jobStatus", triggerState.name());
if (trigger instanceof CronTrigger) {
CronTrigger cronTrigger = (CronTrigger) trigger;
String cronExpression = cronTrigger.getCronExpression();
map.put("jobTime", cronExpression);
}
return map;
}
}
6.创建控制层
package com.example.hellodemo.controller;
import com.example.hellodemo.bean.OperationModel;
import com.example.hellodemo.bean.QuartzModel;
import com.example.hellodemo.service.QuartzService;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* @author qx
* @date 2023/11/27
* @des Quartz控制层
*/
@RestController
@RequestMapping("/quartz")
public class QuartzController {
@Autowired
private QuartzService quartzService;
/**
* 新增定时任务
*/
@PostMapping("/add")
public Boolean addTask(@RequestBody QuartzModel quartzModel) throws SchedulerException {
return quartzService.addJob(quartzModel);
}
/**
* 暂停定时任务
*/
@PostMapping("/pause")
public Boolean pauseTask(@RequestBody OperationModel operationModel) throws SchedulerException {
return quartzService.pauseJob(operationModel);
}
/**
* 恢复定时任务
*/
@PostMapping("/resume")
public Boolean resumeTask(@RequestBody OperationModel operationModel) throws SchedulerException {
return quartzService.resumeJob(operationModel);
}
/**
* 删除定时任务
*/
@PostMapping("/delete")
public Boolean deleteTask(@RequestBody OperationModel operationModel) throws SchedulerException {
return quartzService.deleteJob(operationModel);
}
/**
* 查看所有定时任务
*/
@GetMapping("/queryAllJob")
public List<Map<String, Object>> queryAllJob() throws SchedulerException {
return quartzService.queryAllJob();
}
/**
* 查看所有正在运行的定时任务
*/
@GetMapping("/queryAllRunningJob")
public List<Map<String, Object>> queryAllRunningJob() throws SchedulerException {
return quartzService.queryAllRunningJob();
}
}
三、测试
1.添加定时任务1
2023-11-27 10:12:08.885 INFO 10392 --- [nio-8080-exec-1] c.e.h.service.impl.QuartzServiceImpl : quartzModel:QuartzModel(cron=*/5 * * * * ?, jobGroup=job_group_task1, jobName=job_name_task1, triggerGroup=trigger_group_task1, triggerName=trigger_task1, jobData=JobDetailModel(content=定时任务自定义内容, type=1))
2023-11-27 10:12:08.928 INFO 10392 --- [nio-8080-exec-1] org.quartz.core.QuartzScheduler : Scheduler clusteredScheduler_$_NON_CLUSTERED paused.
2023-11-27 10:12:08.928 INFO 10392 --- [nio-8080-exec-1] c.e.h.service.impl.QuartzServiceImpl : ----定时任务成功添加进quartz队列中----
2023-11-27 10:12:10.044 INFO 10392 --- [eduler_Worker-1] com.example.hellodemo.task.SimpleTask : 开始执行定时任务...........
2023-11-27 10:12:10.044 INFO 10392 --- [eduler_Worker-1] com.example.hellodemo.task.SimpleTask : task1被执行,content:定时任务自定义内容
2023-11-27 10:12:15.046 INFO 10392 --- [eduler_Worker-1] com.example.hellodemo.task.SimpleTask : -------task1定时任务执行结束------
2023-11-27 10:12:15.046 INFO 10392 --- [eduler_Worker-1] com.example.hellodemo.task.SimpleTask : 定时任务执行结束..............
2.添加定时任务2
我们查询数据库,发现添加的定时任务添加到了数据库对应的表之中。
3.获取所有定时任务
4.获取所有正在运行的定时任务
5.暂停定时任务
我们再次查询正在运行的定时任务,发现只有一个正在运行的定时任务了。task2被我们暂停了。
6.恢复定时任务
继续查询正在运行的定时任务,发现又有两个正在运行的定时任务了。我们查看日志也可以看到两个定时任务在同时运行。
7.删除定时任务
获取所有定时任务,发现task2的定时任务已经被删除了。
我们在控制台上发现只有task1定时任务相关的日志了。
对应的数据库表中的数据也已经被删除掉了。