# Springboot-Quartz
**Repository Path**: hope4cc/springboot-quartz
## Basic Information
- **Project Name**: Springboot-Quartz
- **Description**: SpringBoot整合Quartz
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2022-11-22
- **Last Updated**: 2023-03-16
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 一、概述
:::warning
模拟 定时删除不用的数据。如删除一年前的门禁进出记录
:::
```sql
#闸门表
DROP TABLE IF EXISTS `device`;
CREATE TABLE `device` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`location` varchar(255) DEFAULT NULL,
`state` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `id` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
BEGIN;
INSERT INTO `device` VALUES (2, 'west_1_in', '西门-1号闸机-出', 'stop');
INSERT INTO `device` VALUES (3, 'face_recognition', '南门-1号闸机-入', 'stop');
INSERT INTO `device` VALUES (5, 'east_1_out', '东门-1号闸机-出', 'stop');
COMMIT;
#进出记录表
DROP TABLE IF EXISTS `records`;
CREATE TABLE `records` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`uid` int(11) unsigned DEFAULT NULL,
`did` int(11) unsigned DEFAULT NULL,
`time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
) ENGINE=InnoDB AUTO_INCREMENT=135 DEFAULT CHARSET=utf8mb4;
BEGIN;
INSERT INTO `records` VALUES (71, 21303206,3, '2021-05-26 10:04:43');
INSERT INTO `records` VALUES (72, 21303206,3, '2021-05-26 10:04:54');
INSERT INTO `records` VALUES (73, 21303206,3, '2021-05-26 10:05:01');
INSERT INTO `records` VALUES (74, 21303206,3, '2021-05-26 10:05:20');
INSERT INTO `records` VALUES (75, 21303206,3, '2021-05-26 10:05:50');
INSERT INTO `records` VALUES (76, 21303206,3, '2021-05-26 10:05:55');
INSERT INTO `records` VALUES (77, 21303206,3, '2021-05-26 10:06:39');
INSERT INTO `records` VALUES (78, 21303206,3, '2021-05-26 10:06:40');
INSERT INTO `records` VALUES (79, 21303206,3, '2021-05-26 10:07:03');
INSERT INTO `records` VALUES (80, 21303206,3, '2021-05-28 15:06:54');
INSERT INTO `records` VALUES (81, 21303206,3, '2021-05-28 15:06:58');
INSERT INTO `records` VALUES (82, 21303206,3, '2021-05-28 15:07:09');
INSERT INTO `records` VALUES (83, 21303206,3, '2021-05-28 15:07:22');
INSERT INTO `records` VALUES (84, 21303206,3, '2021-05-28 15:07:30');
INSERT INTO `records` VALUES (85, 21303206,3, '2021-05-28 15:07:34');
INSERT INTO `records` VALUES (86, 21303206,3, '2021-05-28 15:07:38');
INSERT INTO `records` VALUES (87, 21303206,3, '2021-06-03 16:44:22');
INSERT INTO `records` VALUES (88, 21303206,3, '2021-06-03 16:44:26');
INSERT INTO `records` VALUES (89, 21303206,3, '2021-06-03 16:44:35');
INSERT INTO `records` VALUES (90, 21303206,3, '2021-06-03 16:44:59');
INSERT INTO `records` VALUES (91, 21303206,3, '2021-06-03 16:45:03');
INSERT INTO `records` VALUES (92, 21303206,3, '2021-06-03 16:45:07');
INSERT INTO `records` VALUES (93, 21303206,3, '2021-06-03 16:45:17');
INSERT INTO `records` VALUES (110,21303206, 3, '2021-06-05 08:51:35');
INSERT INTO `records` VALUES (111,21303206, 3, '2021-06-05 08:51:45');
INSERT INTO `records` VALUES (112,21303206, 3, '2021-06-05 08:51:59');
INSERT INTO `records` VALUES (113,21303206, 3, '2021-06-05 08:53:18');
INSERT INTO `records` VALUES (114,21303206, 3, '2021-06-05 09:08:08');
INSERT INTO `records` VALUES (115,21303206, 3, '2021-06-05 09:13:51');
INSERT INTO `records` VALUES (116,21303206, 3, '2021-06-05 09:16:42');
INSERT INTO `records` VALUES (117,21303206, 3, '2021-06-05 09:16:50');
INSERT INTO `records` VALUES (118,21303206, 3, '2021-06-05 09:17:44');
INSERT INTO `records` VALUES (119, 21303201, 3, '2021-06-05 09:19:15');
INSERT INTO `records` VALUES (120, 21303201, 3, '2021-06-08 19:45:35');
INSERT INTO `records` VALUES (121, 21303201, 3, '2021-06-08 19:45:39');
INSERT INTO `records` VALUES (122, 21303201, 3, '2021-06-08 19:45:43');
INSERT INTO `records` VALUES (123, 21303201, 3, '2021-06-08 19:45:47');
INSERT INTO `records` VALUES (124, 21303201, 3, '2021-06-08 19:45:51');
INSERT INTO `records` VALUES (125, 21303201, 3, '2021-06-08 19:45:55');
INSERT INTO `records` VALUES (126, 21303201, 3, '2021-06-08 19:45:59');
INSERT INTO `records` VALUES (127, 21303201, 3, '2021-06-08 19:46:03');
INSERT INTO `records` VALUES (128, 21303201, 3, '2021-06-08 19:46:07');
INSERT INTO `records` VALUES (129, 21303201, 3, '2021-06-08 19:46:23');
INSERT INTO `records` VALUES (130, 21303201, 3, '2021-06-08 19:46:29');
INSERT INTO `records` VALUES (131, 21303201, 3, '2021-06-08 19:46:55');
INSERT INTO `records` VALUES (132, 21303201, 3, '2021-06-08 19:46:59');
INSERT INTO `records` VALUES (133, 21303201, 3, '2021-06-08 19:47:03');
INSERT INTO `records` VALUES (134, 21303201, 3, '2021-06-08 19:47:07');
COMMIT;
#用户表
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户唯一id',
`name` varchar(255) DEFAULT NULL COMMENT '姓名',
`flag` int(1) DEFAULT '0' COMMENT '注销标志,1:注销,0:正常',
`time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;
BEGIN;
INSERT INTO `user` VALUES (21303201, '菜菜',1);
INSERT INTO `user` VALUES (21303202, '苏苏',1);
COMMIT;
```
:::warning
**quartz包含的主要接口如下:**
Scheduler 代表调度容器,一个调度容器中可以注册多个JobDetail和Trigger。
Job 代表工作,即要执行的具体内容。
JobDetail 代表具体的可执行的调度程序,Job是这个可执行程调度程序所要执行的内容。
JobBuilder 用于定义或构建JobDetail实例。
Trigger 代表调度触发器,决定什么时候去调。
TriggerBuilder 用于定义或构建触发器。
JobStore 用于存储作业和任务调度期间的状态。
:::
# 二、Springboot 整合 Quartz
### 1、数据库表准备
#### 1.1、Quartz 的作业存储类型
RAMJobStore:
RAM 也就是内存,默认情况下 Quartz 会将任务调度存储在内存中,这种方式性能是最好的,因为内存的速度是最快的。不好的地方就是数据缺乏持久性,但程序崩溃或者重新发布的时候,所有运行信息都会丢失
JDBC 作业存储:
存到数据库之后,可以做单点也可以做集群,当任务多了之后,可以统一进行管理,随时停止、暂停、修改任务。关闭或者重启服务器,运行的信息都不会丢失。缺点就是运行速度快慢取决于连接数据库的快慢
Quartz 存储任务信息有两种方式,使用内存或者使用数据库来存储,这里我采用 MySQL 数据库存储的方式,首先需要新建 Quartz 的相关表
sql 脚本下载地址:[http://www.quartz-scheduler.org/downloads/](http://www.quartz-scheduler.org/downloads/),名称为 tables_mysql.sql
创建成功后数据库中多出 11 张表

#### 1.2、Maven 主要依赖
```xml
org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.2.2
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
com.mysql
mysql-connector-j
runtime
org.quartz-scheduler
quartz
2.3.2
org.quartz-scheduler
quartz-jobs
2.3.2
org.springframework.boot
spring-boot-starter-data-redis
com.alibaba
druid-spring-boot-starter
1.2.8
com.github.pagehelper
pagehelper-spring-boot-starter
1.3.0
cn.hutool
hutool-all
5.3.10
org.jetbrains
annotations
13.0
compile
```
:::warning
这里使用 druid 作为数据库连接池,Quartz 默认使用 c3p0
:::
### 2、配置文件
#### 2.1、quartz.properties
:::warning
默认情况下,Quartz 会加载 classpath 下的 quartz.properties 作为配置文件。如果找不到,则会使用 quartz 框架自己 jar 包下 org/quartz 底下的 quartz.properties 文件
:::

```properties
#主要分为scheduler、threadPool、jobStore、dataSource等部分
org.quartz.scheduler.instanceId=AUTO
org.quartz.scheduler.instanceName=MyQuartzScheduler
#如果您希望Quartz Scheduler通过RMI作为服务器导出本身,则将“rmi.export”标志设置为true
#在同一个配置文件中为'org.quartz.scheduler.rmi.export'和'org.quartz.scheduler.rmi.proxy'指定一个'true'值是没有意义的,如果你这样做'export'选项将被忽略
org.quartz.scheduler.rmi.export=false
#如果要连接(使用)远程服务的调度程序,则将“org.quartz.scheduler.rmi.proxy”标志设置为true。您还必须指定RMI注册表进程的主机和端口 - 通常是“localhost”端口1099
org.quartz.scheduler.rmi.proxy=false
org.quartz.scheduler.wrapJobExecutionInUserTransaction=false
#实例化ThreadPool时,使用的线程类为SimpleThreadPool
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
#threadCount和threadPriority将以setter的形式注入ThreadPool实例
#并发个数 如果你只有几个工作每天触发几次 那么1个线程就可以,如果你有成千上万的工作,每分钟都有很多工作 那么久需要50-100之间.
#只有1到100之间的数字是非常实用的
org.quartz.threadPool.threadCount=5
#优先级 默认值为5
org.quartz.threadPool.threadPriority=5
#可以是“true”或“false”,默认为false
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
#在被认为“misfired”(失火)之前,调度程序将“tolerate(容忍)”一个Triggers(触发器)将其下一个启动时间通过的毫秒数。默认值(如果您在配置中未输入此属性)为60000(60秒)
org.quartz.jobStore.misfireThreshold=5000
# 默认存储在内存中,RAMJobStore快速轻便,但是当进程终止时,所有调度信息都会丢失
#org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
#持久化方式,默认存储在内存中,此处使用数据库方式
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
#您需要为JobStore选择一个DriverDelegate才能使用。DriverDelegate负责执行特定数据库可能需要的任何JDBC工作
# StdJDBCDelegate是一个使用“vanilla”JDBC代码(和SQL语句)来执行其工作的委托,用于完全符合JDBC的驱动程序
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#可以将“org.quartz.jobStore.useProperties”配置参数设置为“true”(默认为false),以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,
#因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题
org.quartz.jobStore.useProperties=true
#表前缀
org.quartz.jobStore.tablePrefix=QRTZ_
#数据源别名,自定义
org.quartz.jobStore.dataSource=qzDS
#使用阿里的druid作为数据库连接池
org.quartz.dataSource.qzDS.connectionProvider.class=com.hope.quartz.config.DruidPoolingconnectionProvider
org.quartz.dataSource.qzDS.URL=jdbc:mysql://127.0.0.1:3306/acs?characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=UTC
org.quartz.dataSource.qzDS.user=root
org.quartz.dataSource.qzDS.password=root
org.quartz.dataSource.qzDS.driver=com.mysql.cj.jdbc.Driver
org.quartz.dataSource.qzDS.maxConnections=10
#设置为“true”以打开群集功能。如果您有多个Quartz实例使用同一组数据库表,则此属性必须设置为“true”,否则您将遇到破坏
#org.quartz.jobStore.isClustered=false
```
#### 2.2、application.yaml
```yaml
server:
ip: localhost
port: 9000
#Spring
spring:
main:
allow-circular-references: true #因循环引用导致启动时报错的问题
datasource:
#1.JDBC
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/acs?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
username: root
password: root
druid:
#2.连接池配置
#初始化连接池的连接数量 大小,最小,最大
initial-size: 3
min-idle: 3
max-active: 10
#配置获取连接等待超时的时间
max-wait: 60000
redis:
host: 1.15.87.229
port: 6379
password: 123
database: 1
mybatis:
mapper-locations: classpath:mapper/*.xml #mybatis
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
```
#### 2.3、quartz 配置类 QuartzConfig
```java
@Configuration
public class QuartzConfig implements SchedulerFactoryBeanCustomizer {
@Bean
public Properties properties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
// 对quartz.properties文件进行读取
propertiesFactoryBean.setLocation(new ClassPathResource("/myquartz.properties"));
// 在quartz.properties中的属性被读取并注入后再初始化对象
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
@Bean
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setQuartzProperties(properties());
return schedulerFactoryBean;
}
/*
* quartz初始化监听器
*/
@Bean
public QuartzInitializerListener executorListener() {
return new QuartzInitializerListener();
}
/*
* 通过SchedulerFactoryBean获取Scheduler的实例
*/
@Bean
public Scheduler scheduler() throws IOException {
return schedulerFactoryBean().getScheduler();
}
/**
* 使用阿里的druid作为数据库连接池
*/
@Override
public void customize(@NotNull SchedulerFactoryBean schedulerFactoryBean) {
schedulerFactoryBean.setStartupDelay(2);
schedulerFactoryBean.setAutoStartup(true);
schedulerFactoryBean.setOverwriteExistingJobs(true);
}
}
```
### 3、业务
#### 3.1、创建任务类
:::warning
这里模拟 定时删除不用的数据。如删除一年前的门禁进出记录
:::
:::warning
#### 删除一年前的数据
DELETE FROM `records` where year(time)=year(date_sub(now(),interval 1 year));
:::
```java
@Component
public class DeleteRecords extends QuartzJobBean {
public static DeleteRecords DeleteRecords;
@PostConstruct
public void init() {
DeleteRecords = this;
}
@Autowired
private RecordsServiceImpl recordService;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
try {
DeleteRecords.recordService.delete();
log.info("调用任务成功,删除一年前的闸门出入记录");
} catch (Exception e) {
e.printStackTrace();
log.error("调用任务失败");
}
log.info("执行时间: " + DateUtil.now());
}
}
```
#### 3.2、 Service 层
```java
package com.hope.quartz.service.Impl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.hope.quartz.dto.JobAndTriggerDto;
import com.hope.quartz.job.DeleteRecords;
//import com.hope.quartz.job.HelloJob;
import com.hope.quartz.mapper.JobDetailMapper;
import com.hope.quartz.service.QuartzService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 文件名:QuartzServiceImpl
* 创建者:hope
* 邮箱:1602774287@qq.com
* 微信:hope4cc
* 创建时间:2022/11/21-22:46
* 描述:
*/
@Slf4j
@Service
public class QuartzServiceImpl implements QuartzService {
private JobDetailMapper jobDetailMapper;
private Scheduler scheduler;
@Autowired
public QuartzServiceImpl(JobDetailMapper jobDetailMapper, Scheduler scheduler) {
this.jobDetailMapper = jobDetailMapper;
this.scheduler = scheduler;
}
@Override
public PageInfo getJobAndTriggerDetails(Integer pageNum, Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
List list = jobDetailMapper.getJobAndTriggerDetails();
PageInfo pageInfo = new PageInfo<>(list);
return pageInfo;
}
/**
* 新增定时任务
*
* @param jName 任务名称
* @param jGroup 任务组
* @param tName 触发器名称
* @param tGroup 触发器组
* @param cron cron表达式
*/
@Override
public void addjob(String jName, String jGroup, String tName, String tGroup, String cron) {
try {
// 构建JobDetail
JobDetail jobDetail = JobBuilder.newJob(DeleteRecords.class)
.withIdentity(jName, jGroup)
.build();
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(tName, tGroup)
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(cron))
.build();
// 启动调度器
scheduler.start();
scheduler.scheduleJob(jobDetail, trigger);
} catch (Exception e) {
log.info("创建定时任务失败" + e);
}
}
@Override
public void pausejob(String jName, String jGroup) throws SchedulerException {
scheduler.pauseJob(JobKey.jobKey(jName, jGroup));
}
@Override
public void resumejob(String jName, String jGroup) throws SchedulerException {
scheduler.resumeJob(JobKey.jobKey(jName, jGroup));
}
@Override
public void rescheduleJob(String jName, String jGroup, String cron) throws SchedulerException {
TriggerKey triggerKey = TriggerKey.triggerKey(jName, jGroup);
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
// 按新的trigger重新设置job执行,重启触发器
scheduler.rescheduleJob(triggerKey, trigger);
}
@Override
public void deletejob(String jName, String jGroup) throws SchedulerException {
scheduler.pauseTrigger(TriggerKey.triggerKey(jName, jGroup));
scheduler.unscheduleJob(TriggerKey.triggerKey(jName, jGroup));
scheduler.deleteJob(JobKey.jobKey(jName, jGroup));
}
}
```
#### 3.3、Controller 层
```java
package com.hope.quartz.controller;
import com.github.pagehelper.PageInfo;
import com.hope.quartz.comon.RespBean;
import com.hope.quartz.comon.ResponseCode;
import com.hope.quartz.dto.JobAndTriggerDto;
import com.hope.quartz.service.QuartzService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 文件名:QuartzController
* 创建者:hope
* 邮箱:1602774287@qq.com
* 微信:hope4cc
* 创建时间:2022/11/21-23:01
* 描述:
*/
@Slf4j
@RestController
@RequestMapping("/quartz")
public class QuartzController {
@Autowired
public QuartzController(QuartzService quartzService) {
this.quartzService = quartzService;
}
private QuartzService quartzService;
/**
* 新增定时任务
*
* @param jName 任务名称
* @param jGroup 任务组
* @param tName 触发器名称
* @param tGroup 触发器组
* @param cron cron表达式
* @return ResultMap
*/
@PostMapping(path = "/addjob")
@ResponseBody
public RespBean addjob(String jName, String jGroup, String tName, String tGroup, String cron) {
try {
quartzService.addjob(jName, jGroup, tName, tGroup, cron);
return new RespBean(ResponseCode.SUCCESS,"添加任务成功");
} catch (Exception e) {
e.printStackTrace();
return new RespBean(ResponseCode.ERROR,"添加任务失败");
}
}
/**
* 暂停任务
*
* @param jName 任务名称
* @param jGroup 任务组
* @return ResultMap
*/
@PostMapping(path = "/pausejob")
@ResponseBody
public RespBean pausejob(String jName, String jGroup) {
try {
quartzService.pausejob(jName, jGroup);
return new RespBean(ResponseCode.SUCCESS,"暂停任务成功");
} catch (SchedulerException e) {
e.printStackTrace();
return new RespBean(ResponseCode.ERROR,"暂停任务失败");
}
}
/**
* 恢复任务
*
* @param jName 任务名称
* @param jGroup 任务组
* @return ResultMap
*/
@PostMapping(path = "/resumejob")
@ResponseBody
public RespBean resumejob(String jName, String jGroup) {
try {
quartzService.resumejob(jName, jGroup);
return new RespBean(ResponseCode.SUCCESS,"恢复任务成功");
} catch (SchedulerException e) {
e.printStackTrace();
return new RespBean(ResponseCode.ERROR,"恢复任务失败");
}
}
/**
* 重启任务
*
* @param jName 任务名称
* @param jGroup 任务组
* @param cron cron表达式
* @return ResultMap
*/
@PostMapping(path = "/reschedulejob")
@ResponseBody
public RespBean rescheduleJob(String jName, String jGroup, String cron) {
try {
quartzService.rescheduleJob(jName, jGroup, cron);
return new RespBean(ResponseCode.SUCCESS,"重启任务成功");
} catch (SchedulerException e) {
e.printStackTrace();
return new RespBean(ResponseCode.ERROR,"重启任务失败");
}
}
/**
* 删除任务
*
* @param jName 任务名称
* @param jGroup 任务组
* @return ResultMap
*/
@PostMapping(path = "/deletejob")
@ResponseBody
public RespBean deletejob(String jName, String jGroup) {
try {
quartzService.deletejob(jName, jGroup);
return new RespBean(ResponseCode.SUCCESS,"删除任务成功");
} catch (SchedulerException e) {
e.printStackTrace();
return new RespBean(ResponseCode.ERROR,"删除任务失败");
}
}
/**
* 查询任务
*
* @param pageNum 页码
* @param pageSize 每页显示多少条数据
* @return Map
*/
@GetMapping(path = "/queryjob")
@ResponseBody
public RespBean queryjob(Integer pageNum, Integer pageSize) {
PageInfo pageInfo = quartzService.getJobAndTriggerDetails(pageNum, pageSize);
Map map = new HashMap<>();
if (!StringUtils.isEmpty(pageInfo.getTotal())) {
map.put("JobAndTrigger", pageInfo);
map.put("number", pageInfo.getTotal());
return new RespBean(ResponseCode.SUCCESS,map,"查询任务成功");
}
return new RespBean(ResponseCode.ERROR,"查询任务成功失败,没有数据");
}
}
```
#### 3.4、接口测试
#### 3.4.1、新增定时任务
postman 测试如下
为了测试方便,cron选择5秒钟执行一次



# 三、其他
### 1、cron表达式
```
//cron表达式的用法;
//秒 分 时 日 月 周几
//0 * * * * MON-FRI
结构
corn从左到右(用空格隔开):秒 分 小时 月份中的日期 月份 星期中的日期 年份
工具:https://cron.qqe2.com/
常用的表达式
(1)0/2 * * * * ? 表示每2秒 执行任务
(1)0 0/2 * * * ? 表示每2分钟 执行任务
(1)0 0 2 1 * ? 表示在每月的1日的凌晨2点调整任务
(2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业
(3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作
(4)0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
(5)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
(6)0 0 12 ? * WED 表示每个星期三中午12点
(7)0 0 12 * * ? 每天中午12点触发
(8)0 15 10 ? * * 每天上午10:15触发
(9)0 15 10 * * ? 每天上午10:15触发
(10)0 15 10 * * ? 每天上午10:15触发
(11)0 15 10 * * ? 2005 2005年的每天上午10:15触发
(12)0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发
(13)0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
(14)0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
(15)0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
(16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发
(17)0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
(18)0 15 10 15 * ? 每月15日上午10:15触发
(19)0 15 10 L * ? 每月最后一日的上午10:15触发
(20)0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发
(21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发
(22)0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发
```
### 2、触发内容
每1小时触发一次定时器:控制台输出内容“我被成功触发拉(每1小时)” **0 0 /1 * * ? **
每2分钟触发一次定时器:控制台输出内容“我被成功触发拉(每2分钟)”**0 0/2 * * * ?**
每5秒钟触发一次定时器:控制台输出内容“我被成功触发拉(每5秒钟)” **0/5 * * * * ?**
每天凌晨1:30触发一次定时器:控制台输出内容“我被成功触发拉(凌晨1:30) **0 30 1 * * ?**
#### Spring自带的定时器 spring task
:::warning
优点:spring框架自带的定时功能,springboot做了非常好的封装,开启和定义定时任务非常容易,支持复杂的 cron 表达式,可以满足绝大多数单机版的业务场景。单个任务时,当前次的调度完成后,再执行下一次任务调度。
缺点:默认单线程,如果前面的任务执行时间太长,对后面任务的执行有影响。不支持集群方式部署,不能做数据存储型定时任务。
:::
```java
/**
* Spring自带的定时器
* 启动类上需要加入注解@EnableScheduling
* import org.springframework.scheduling.annotation.Scheduled;
*/
//每1小时触发一次定时器:控制台输出内容“我被成功触发拉(每1小时)”
@Scheduled(cron = "0 30 1 * * ?")
public void Everyday() {
String format= goDateTime(new Date());
System.out.println("我被成功触发拉(凌晨1:30)"+format);
}
//测试 控制台输出内容“我被成功触发拉(每2分钟)”
@Scheduled(cron = "0 0/2 * * * ?")
public void testTwoMinutes() {
String format= goDateTime(new Date());
System.out.println("我被成功触发拉(每2分钟)"+format);
}
//测试 每隔5秒触发一次
@Scheduled(cron = "0/5 * * * * ?")
public void testFiveSeconds() {
String format= goDateTime(new Date());
System.out.println("我被成功触发拉(每5秒钟)"+format);
}
//每天凌晨1:30触发一次定时器:控制台输出内容“我被成功触发拉(凌晨1:30)
@Scheduled(cron = "0 30 1 * * ?")
public void Everyday() {
String format= goDateTime(new Date());
System.out.println("我被成功触发拉(凌晨1:30)"+format);
}
//日期格式
public static String goDateTime(Date datetime){
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
String thisDateTime = df.format(datetime);
return thisDateTime;
}
```

#### springboot 集成 quartz
:::warning
使用 spring quartz 的优缺点:
优点:默认是多线程异步执行,单个任务时,在上一个调度未完成时,下一个调度时间到时,会另起一个线程开始新的调度,多个任务之间互不影响。支持复杂的 cron 表达式,它能被集群实例化,支持分布式部署。
缺点:相对于spring task实现定时任务成本更高,需要手动配置 QuartzJobBean 、 JobDetail和 Trigger 等。需要引入了第三方的 quartz 包,有一定的学习成本。不支持并行调度,不支持失败处理策略和动态分片的策略等。
:::
pom.xml文件中引入 quartz 相关依赖。
```java
org.springframework.boot
spring-boot-starter-quartz
```
```java
创建真正的定时任务执行类,该类继承 QuartzJobBean 。
public class QuartzTestJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
String quartzValue = (String) context.getJobDetail().getJobDataMap().get("quartz");
System.out.println("quartz:" + quartzValue);
}
}
创建调度程序 JobDetail 和调度器 Trigger 。
/**
* 创建定时任务
*/
@Bean
public JobDetail quartzTestDetail() {
JobDetail jobDetail = JobBuilder.newJob(QuartzTestJob.class)
.withIdentity("quartzTestDetail", "QUARTZ_TEST")
.usingJobData("quartz", "我被成功触发拉(每5秒钟)"+new Date())
.storeDurably()
.build();
return jobDetail;
}
/**
* 创建触发器
*/
@Bean
public Trigger quartzTestJobTrigger() {
//每隔5秒执行一次 spring quartz 跟 spring task 的 cron 表达式规则基本一致
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
//创建触发器
Trigger trigger = TriggerBuilder.newTrigger()
.forJob(quartzTestDetail())
.withIdentity("quartzTestJobTrigger", "QUARTZ_TEST_JOB_TRIGGER")
.withSchedule(cronScheduleBuilder)
.build();
return trigger;
}
```
# 四、遇到的问题解决
### 非spring管理的工具类使用@Autowired注解注入DAO为null的问题
:::warning
使用**@Component**注解将工具类声明为spring组件,并**静态初始化**。
:::
```java
//静态初始化
public static DeleteRecords DeleteRecords;
//保证Bean初始化前已经装配了属性
@PostConstruct
public void init() {
DeleteRecords = this;
}
@Autowired
private RecordsServiceImpl recordService;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
try {
DeleteRecords.recordService.delete();//使用
log.info("调用任务成功,删除一年前的闸门出入记录");
} catch (Exception e) {
e.printStackTrace();
log.error("调用任务失败");
}
log.info("执行时间: " + DateUtil.now());
}
```
### Active Scheduler of name 'MyClusterScheduler' already registered in Quartz SchedulerRepository.
:::warning
工程里面我的quartz的配置文件名为quartz.properties,跟Quartz默认的配置文件名quartz.properties相同。导致工程启动的时候,我本地的quartz配置文件覆盖了系统的配置文件。
解决[https://blog.csdn.net/Nancy50/article/details/104017171](https://blog.csdn.net/Nancy50/article/details/104017171)
:::