SpringBoot项目集成QuartzJob任务

场景描述

在项目的实际场景中,我们经常会遇到一些任务需要每天、每周、或者固定时间去执行,所以在项目中加入Quartz框架,来更好的对这些事情做管理,只需要配置任务对应的CORN表达式,添加到任务里面即可让他自动化的实现对任务的管理。

集成教程

1. 项目POM文件中引入依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

2. 在项目application.properties中新增如下配置

注意:
1、如果需要quartz 第一次运行时自动生成 quartz 所需的表那么 quartzJob? 后面的配置为 :allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
待第一次运行后可以再根据自己的需要修改
2、配置文件中的 initialize-schema: always 配置的 always 属性意思是,每次初始化都会重新生成表(执行一次删除,执行一次创建),生成后,可以修改为 never
只有以上两个条件同时配置满足,才能使quartz 在第一次运行时,自动生成所需的表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# quartz定时任务,采用数据库方式 如果需要quartz 第一次运行时自动生成 quartz 所需的表那么 quartzJob? 后面的配置为 :allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
spring.quartz.job-store-type=jdbc
# ?配置文件中的 initialize-schema: always 配置的 always 属性意思是,每次初始化都会重新生成表(执行一次删除,执行一次创建),生成后,可以修改为 never
spring.quartz.jdbc.initialize-schema=never


# 时任务启动开关,true-开 false-关
spring.quartz.auto-startup=true
#??1???????
spring.quartz.startup-delay=1s

spring.quartz.overwrite-existing-jobs=true
# Quartz Scheduler Properties
spring.quartz.properties.org.quartz.scheduler.instanceName = MyScheduler
spring.quartz.properties.org.quartz.scheduler.instanceId = AUTO

spring.quartz.properties.org.quartz.jobStore.class = org.springframework.scheduling.quartz.LocalDataSourceJobStore
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 = true
spring.quartz.properties.org.quartz.jobStore.misfireThreshold = 12000
spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval = 15000

# ????????
spring.quartz.properties.org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
spring.quartz.properties.org.quartz.threadPool.threadCount = 1
spring.quartz.properties.org.quartz.threadPool.threadPriority = 5
spring.quartz.properties.org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

上面配置好之后,启动项目会直接在对应链接的数据库下生成11张默认的表,均是以QRTZ开头,如下图

数据表图

3. 在生成的表上我们还需要新增一张自己添加任务的配置表,具体如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CREATE TABLE `sys_quartz_job` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`create_by` varchar(32) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`del_flag` int(11) DEFAULT NULL COMMENT '删除状态',
`update_by` varchar(32) DEFAULT NULL COMMENT '修改人',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`job_class_name` varchar(255) DEFAULT NULL COMMENT '任务类名',
`cron_expression` varchar(255) DEFAULT NULL COMMENT 'cron表达式',
`parameter` varchar(255) DEFAULT NULL COMMENT '参数',
`meeting_record_id` int(11) DEFAULT NULL COMMENT '会议室记录id',
`description` varchar(255) DEFAULT NULL COMMENT '描述',
`status` int(11) DEFAULT NULL COMMENT '状态 0正常 -1停止',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

4. 添加代码,补充Quartz的功能

此处功能是可以实现对定时任务的管理,比如添加、删除、重新配置、立即执行定时任务等。

Controller类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// Controller类

/**
* @Description: 定时任务
*/
@RestController
@RequestMapping("/sys/quartzJob")
@Slf4j
@Api(tags = "定时任务接口")
public class QuartzJobController {
@Autowired
private QuartzJobService quartzJobService;
@Autowired
private Scheduler scheduler;

/**
* 分页列表查询
*
* @param quartzJob
* @param page
* @param pageSize
* @return
*/
@ApiOperation(value = "分页列表查询", notes = "分页列表查询")
@RequestMapping(value = "/list", method = RequestMethod.GET)
public ApiResult queryPageList(QuartzJob quartzJob, @RequestParam(value = "page", defaultValue = "1") int page,
@RequestParam(value = "pageSize", defaultValue = "20") int pageSize) {
Page<QuartzJob> list = quartzJobService.selectList(quartzJob,page,pageSize);
ApiResult apiResult = new ApiResult();
if (CollectionUtils.isNotEmpty(list.getItems())){
apiResult.setData(list);
}
return ApiResult.ok("list",list);

}

/**
* 添加定时任务
*
* @param quartzJob
* @return
*/
//@RequiresRoles("admin")
@ApiOperation(value = "添加定时任务", notes = "添加定时任务")
@RequestMapping(value = "/add", method = RequestMethod.POST)
public ApiResult add(@RequestBody QuartzJob quartzJob) {
boolean b = quartzJobService.saveAndScheduleJob(quartzJob);
if (b == true) {
return ApiResult.ok("add");
}
return ApiResult.fail("add","添加定时任务失败");
}

/**
* 更新定时任务
*
* @param quartzJob
* @return
*/
//@RequiresRoles("admin")
@ApiOperation(value = "更新定时任务", notes = "更新定时任务")
@RequestMapping(value = "/edit", method ={RequestMethod.PUT, RequestMethod.POST})
public ApiResult eidt(@RequestBody QuartzJob quartzJob) {
try {
quartzJobService.editAndScheduleJob(quartzJob);
} catch (SchedulerException e) {
log.error(e.getMessage(),e);
return ApiResult.fail("edit","更新定时任务失败!");
}
return ApiResult.ok("edit");
}

/**
* 通过id删除
*
* @param id
* @return
*/
@ApiOperation(value = "通过id删除", notes = "通过id删除")
@RequestMapping(value = "/delete", method = RequestMethod.DELETE)
public ApiResult delete(@RequestParam(name = "id", required = true) String id) {
QuartzJob quartzJob = quartzJobService.getById(Long.valueOf(id));
if (quartzJob == null) {
return ApiResult.fail("delete","未找到对应实体");
}
quartzJobService.deleteAndStopJob(Long.valueOf(id));
return ApiResult.ok("delete");

}

/**
* 批量删除
*
* @param ids
* @return
*/
@ApiOperation(value = "批量删除", notes = "批量删除")
@RequestMapping(value = "/deleteBatch", method = RequestMethod.DELETE)
public ApiResult deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
if (ids == null || "".equals(ids.trim())) {
return ApiResult.fail("deleteBatch","参数不识别!");
}
for (String id : Arrays.asList(ids.split(","))) {
QuartzJob job = quartzJobService.getById(Long.valueOf(id));
quartzJobService.deleteAndStopJob(Long.valueOf(id));
}
return ApiResult.ok("deleteBatch");
}

/**
* 暂停定时任务
*
* @param id
* @return
*/
@GetMapping(value = "/pause")
@ApiOperation(value = "停止定时任务")
public ApiResult pauseJob(@RequestParam(name = "id") String id) {
QuartzJob job = quartzJobService.getById(Long.valueOf(id));
if (job == null) {
return ApiResult.fail("pause","定时任务不存在!");
}
quartzJobService.pause(job);
return ApiResult.ok("pause");
}

/**
* 启动定时任务
*
* @param id
* @return
*/
@GetMapping(value = "/resume")
@ApiOperation(value = "启动定时任务")
public ApiResult resumeJob(@RequestParam(name = "id") String id) {
QuartzJob job = quartzJobService.getById(Long.valueOf(id));
if (job == null) {
return ApiResult.fail("resume","定时任务不存在!");
}
quartzJobService.resumeJob(job);
//scheduler.resumeJob(JobKey.jobKey(job.getJobClassName().trim()));
return ApiResult.ok("resume");
}

/**
* 通过id查询
*
* @param id
* @return
*/
@ApiOperation(value = "通过id查询", notes = "通过id查询")
@RequestMapping(value = "/queryById", method = RequestMethod.GET)
public ApiResult queryById(@RequestParam(name = "id", required = true) String id) {
QuartzJob quartzJob = quartzJobService.getById(Long.valueOf(id));
ApiResult apiResult = new ApiResult();
apiResult.setData(quartzJob);
return apiResult;
}

/**
* 立即执行
* @param id
* @return
*/
@ApiOperation(value = "立即执行", notes = "立即执行")
@GetMapping("/execute")
public ApiResult execute(@RequestParam(name = "id", required = true) String id) {
QuartzJob quartzJob = quartzJobService.getById(Long.valueOf(id));
if (quartzJob == null) {
return ApiResult.fail("execute","未找到对应实体");
}
try {
quartzJobService.execute(quartzJob);
} catch (Exception e) {
//e.printStackTrace();
log.info("定时任务 立即执行失败>>"+e.getMessage());
return ApiResult.fail("execute","执行失败!");
}
return ApiResult.ok("execute");
}

}


Service类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
@Service
@Slf4j
public class QuartzJobService{

@Resource
private QuartzJobMapper quartzJobMapper;
@Autowired
private Scheduler scheduler;

/**
* 立即执行的任务分组
*/
private static final String JOB_TEST_GROUP = "test_group";

public Page<QuartzJob> selectList(QuartzJob quartzJob, int page, int pageSize) {
Page<QuartzJob> pageQuartzJobVo = Page.create(page, pageSize);
Example example = new Example(QuartzJob.class);
Example.Criteria criteria = example.createCriteria();
if (!StringUtils.isEmpty(quartzJob.getJobClassName())) {
criteria.andLike("jobClassName","%"+ quartzJob.getJobClassName() +"%");
}
if (!StringUtils.isEmpty(quartzJob.getCronExpression())) {
criteria.andEqualTo("cronExpression", quartzJob.getCronExpression());
}
if (!StringUtils.isEmpty(quartzJob.getDescription())) {
criteria.andLike("", "%"+quartzJob.getDescription() +"%");
}
// 查询未删除的定时列表
criteria.andEqualTo("delFlag",CommonConstant.DEL_FLAG_0.getCode());
List<QuartzJob> quartzJobs = quartzJobMapper.selectByExample(example);
pageQuartzJobVo.setItems(quartzJobs);
pageQuartzJobVo.setTotalCount(quartzJobs.size());
return pageQuartzJobVo;
}

public boolean saveAndScheduleJob(QuartzJob quartzJob) {
// DB设置修改
quartzJob.setDelFlag(CommonConstant.DEL_FLAG_0.getCode());
boolean success = this.save(quartzJob);
if (success) {
if (CommonConstant.STATUS_NORMAL.getCode().equals(quartzJob.getStatus())) {
// 定时器添加
this.schedulerAdd(String.valueOf(quartzJob.getId()), quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim(), quartzJob.getParameter());
}
}
return success;
}

private boolean save(QuartzJob quartzJob) {
if (StringUtils.isEmpty(quartzJob.getCreateBy())){
quartzJob.setCreateBy("admin");
}
if (StringUtils.isEmpty(quartzJob.getCreateTime())){
quartzJob.setCreateTime(new Date());
}
int i = quartzJobMapper.insertSelective(quartzJob);
if (i == 1){
return true;
}
return false;
}

public void editAndScheduleJob(QuartzJob quartzJob) throws SchedulerException {
if (CommonConstant.STATUS_NORMAL.equals(quartzJob.getStatus())) {
schedulerDelete(quartzJob.getId());
schedulerAdd(String.valueOf(quartzJob.getId()), quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim(), quartzJob.getParameter());
}else{
scheduler.pauseJob(JobKey.jobKey(String.valueOf(quartzJob.getId())));
}
this.updateById(quartzJob);
}

private void updateById(QuartzJob quartzJob) {
QuartzJob q = new QuartzJob();
q.setId(quartzJob.getId());
if (!StringUtils.isEmpty(quartzJob.getCronExpression())){
q.setCronExpression(quartzJob.getCronExpression());
}
if (!StringUtils.isEmpty(quartzJob.getJobClassName())){
q.setJobClassName(quartzJob.getJobClassName());
}
if (!StringUtils.isEmpty(quartzJob.getDescription())){
q.setDescription(quartzJob.getDescription());
}
if (!StringUtils.isEmpty(quartzJob.getDelFlag())){
q.setDelFlag(quartzJob.getDelFlag());
}
if (!StringUtils.isEmpty(quartzJob.getStatus())){
q.setStatus(quartzJob.getStatus());
}
if (!StringUtils.isEmpty(quartzJob.getParameter())){
q.setParameter(quartzJob.getParameter());
}
q.setUpdateBy("admin");
q.setUpdateTime(new Date());
quartzJobMapper.updateByPrimaryKeySelective(q);
}

/**
* 添加定时任务
*
* @param jobClassName
* @param cronExpression
* @param parameter
*/
private void schedulerAdd(String id, String jobClassName, String cronExpression, String parameter) {
try {
// 启动调度器
scheduler.start();

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

// 表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);

// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(id).withSchedule(scheduleBuilder).build();

scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
throw new BusinessException("创建定时任务失败" + e);
} catch (RuntimeException e) {
throw new BusinessException(e.getMessage() + e);
}catch (Exception e) {
throw new BusinessException("后台找不到该类名:" + jobClassName + e);
}
}

/**
* (删除&停止)删除定时任务
*/
public boolean deleteAndStopJob(Long id) {
schedulerDelete(id);
QuartzJob quartzJob = new QuartzJob();
quartzJob.setId(id);
quartzJob.setDelFlag(CommonConstant.DEL_FLAG_1.getCode());
quartzJob.setStatus(CommonConstant.STATUS_UNNORMAL.getCode());
int i = quartzJobMapper.updateByPrimaryKeySelective(quartzJob);
if (i == 1){
return true;
}
return false;
}


/**
* 删除定时任务
*
* @param id
*/
private void schedulerDelete(Long id) {
try {
scheduler.pauseTrigger(TriggerKey.triggerKey(String.valueOf(id)));
scheduler.unscheduleJob(TriggerKey.triggerKey(String.valueOf(id)));
scheduler.deleteJob(JobKey.jobKey(String.valueOf(id)));
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new BusinessException("删除定时任务失败");
}
}

private static Job getClass(String classname) throws Exception {
Class<?> class1 = Class.forName(classname);
return (Job) class1.newInstance();
}


public QuartzJob getById(Long id) {
QuartzJob quartzJob = new QuartzJob();
quartzJob.setId(id);
quartzJob.setStatus(CommonConstant.STATUS_NORMAL.getCode());
QuartzJob quartzJob1 = quartzJobMapper.selectOne(quartzJob);
return quartzJob1;
}


public void pause(QuartzJob quartzJob){
schedulerDelete(quartzJob.getId());
quartzJob.setStatus(CommonConstant.STATUS_UNNORMAL.getCode());
this.updateById(quartzJob);
}

public void resumeJob(QuartzJob quartzJob) {
schedulerDelete(quartzJob.getId());
schedulerAdd(String.valueOf(quartzJob.getId()), quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim(), quartzJob.getParameter());
quartzJob.setStatus(CommonConstant.STATUS_NORMAL.getCode());
this.updateById(quartzJob);
}

public void execute(QuartzJob quartzJob) throws Exception {
String jobName = quartzJob.getJobClassName().trim();
Date startDate = new Date();
String ymd = DateUtils.format(startDate,"yyyymmddhhmmss");
String identity = jobName + ymd;
//3秒后执行 只执行一次
// update-begin--author:sunjianlei ---- date:20210511--- for:定时任务立即执行,延迟3秒改成0.1秒-------
startDate.setTime(startDate.getTime() + 100L);
// update-end--author:sunjianlei ---- date:20210511--- for:定时任务立即执行,延迟3秒改成0.1秒-------
// 定义一个Trigger
SimpleTrigger trigger = (SimpleTrigger)TriggerBuilder.newTrigger()
.withIdentity(identity, JOB_TEST_GROUP)
.startAt(startDate)
.build();
// 构建job信息
JobDetail jobDetail = JobBuilder.newJob(getClass(jobName).getClass()).withIdentity(identity).usingJobData("parameter", quartzJob.getParameter()).build();
// 将trigger和 jobDetail 加入这个调度
scheduler.scheduleJob(jobDetail, trigger);
// 启动scheduler
scheduler.start();
}
}


Mapper类

1
2
3
4
5
6
7
8
9
10
11
12

public interface QuartzJobMapper extends Mapper<QuartzJob> {

/**
* 根据jobClassName查询
* @param jobClassName 任务类名
* @return
*/
public List<QuartzJob> findByJobClassName(@Param("jobClassName") String jobClassName);
}


实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

@Data
@Table(name = "sys_quartz_job")
public class QuartzJob implements Serializable {
/**
* id
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

/**
* 创建人
*/
@Column(name = "create_by")
private String createBy;

/**
* 创建时间
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Column(name = "create_time")
private Date createTime;

/**
* 删除状态
*/
@Column(name = "del_flag")
private Integer delFlag;

/**
* 修改人
*/
@Column(name = "update_by")
private String updateBy;

/**
* 修改时间
*/
@Column(name = "update_time")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;

/**
* 任务类名
*/
@Column(name = "job_class_name")
private String jobClassName;

/**
* cron表达式
*/
@Column(name = "cron_expression")
private String cronExpression;

/**
* 参数
*/
private String parameter;


/**
* 描述
*/
private String description;

/**
* 状态 0正常 -1停止
*/
private Integer status;
}


5. 简单的Job任务类,这个根据自己的实际需求进行更改

根据实际开发的需要,选择适合自己的任务类搭配即可实现自己想要的效果

任务类一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

/**
* @Description: 同步定时任务测试
*
* 此处的同步是指 当定时任务的执行时间大于任务的时间间隔时
* 会等待第一个任务执行完成才会走第二个任务
*/
@PersistJobDataAfterExecution // 持久化JobDataMap里的数据,使下一个定时任务还能获取到这些值
@DisallowConcurrentExecution // 禁止并发多任务执行,所以永远只有一个任务在执行中
@Slf4j
public class Test1Job implements Job {

@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info(" --- 同步任务调度开始 --- " + " Job Execution key:"+jobExecutionContext.getJobDetail().getKey());
try {
//此处模拟任务执行时间 5秒 任务表达式配置为每秒执行一次:0/1 * * * * ? *
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//测试发现 每5秒执行一次
log.info(" --- 执行完毕,时间:"+new Date()+"---" + " 线程名"+ Thread.currentThread().getName() );
}
}


任务类二

1
2
3
4
5
6
7
8
9
10
11

@Slf4j
public class Test2Job implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info(" Job Execution key:"+jobExecutionContext.getJobDetail().getKey());
log.info(String.format(" rih-health-center 普通定时任务 Test2Job ! 时间:" + new Date()));
}
}


任务类三

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

@Slf4j
public class Test3Job implements Job {

/**
* 若参数变量名修改 QuartzJobController中也需对应修改
*/
private String parameter;

public void setParameter(String parameter) {
this.parameter = parameter;
}
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {

log.info(" Job Execution key:"+jobExecutionContext.getJobDetail().getKey());
log.info( String.format("welcome %s! 带参数定时任务 Test3Job ! 时间:" + new Date(), this.parameter));
}
}


至此,Quartz Job集成完成