Quartz - 定时任务框架

本文最后更新于:2025年2月18日 下午

Quartz - 定时任务框架

以下为单机模式

快速开始

Maven依赖

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

配置文件

1
2
3
4
spring:
quartz: # 定时任务
job-store-type: memory # 存储类型 >> 内存
scheduler-name: MyScheduler # 调度器的名称

配置类

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
package org.example.config;

import org.example.task.MyJob;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* 定时任务Quartz配置文件
* @author peter
* @date 2024/11/12
* @className QuartzConfig
* @packageName org.example.config
*/
@Configuration
public class QuartzConfig {

@Bean
public JobDetail jobDetail() {
return JobBuilder.newJob(MyJob.class)
.withIdentity("myJob")
.storeDurably()
.build();
}

@Bean
public Trigger trigger() {
// 使用cron表达式
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0 30 14 * * ?");
return TriggerBuilder.newTrigger()
.forJob(jobDetail())
.withIdentity("myTrigger")
//.withSchedule(scheduleBuilder) // 这个是固定每天14:30分执行任务!
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(10) // 每 10 秒执行一次
.repeatForever()) // 一直重复
.build();
}
}

作业类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;

@Component
public class MyJob implements Job {

@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("Executing MyJob...");
}
}

Quartz 的 Cron 表达式

Quartz 的 Cron 表达式由 6 个字段组成,具体格式如下:

1
2
秒 分 时 日 月 星期 年(可选)
* * * * * *
  • 秒 (0 - 59): 任务执行的秒数。
  • 分 (0 - 59): 任务执行的分钟数。
  • 时 (0 - 23): 任务执行的小时数。
  • 日 (1 - 31): 任务执行的日子。
  • 月 (1 - 12): 任务执行的月份。
  • 星期 (0 - 6): 任务执行的星期几,0 代表星期天,1 代表星期一。
  • 年 (可选): 任务执行的年份,可以省略。

Quartz Cron 表达式示例

以下是一些常见的 Quartz Cron 表达式示例,包含秒字段:

  1. 每秒执行一次:

    1
    * * * * * ?

    这个表达式表示任务每秒执行一次。

  2. 每分钟执行一次:

    1
    0 * * * * ?

    每分钟的第 0 秒执行一次任务。

  3. 每 5 秒执行一次:

    1
    */5 * * * * ?

    每隔 5 秒执行一次任务。

  4. 每天午夜 12 点执行一次:

    1
    0 0 0 * * ?

    每天凌晨 12 点执行任务。

  5. 每小时的第 15 分钟,第 30 秒执行:

    1
    30 15 * * * ?

    每小时的第 15 分钟、第 30 秒执行任务。

  6. 每 10 秒执行一次:

    1
    */10 * * * * ?

    每 10 秒执行一次任务。

  7. 每月 1 号的午夜 12 点执行:

    1
    0 0 0 1 * ?

    每月的第 1 天午夜 12 点执行任务。

  8. 每周一上午 10 点 30 分 15 秒执行任务:

    1
    15 30 10 ? * 2

    每周一上午 10:30:15 执行任务。

  9. 每个月的最后一天午夜执行:

    1
    0 0 0 L * ?

    每个月的最后一天午夜 12 点执行任务。

  10. 每年 1 月 1 日凌晨 1 点执行:

    1
    0 0 1 1 * ?

    每年 1 月 1 日凌晨 1 点执行任务。

关键点总结:

  • 秒字段 使得 Quartz Cron 表达式比标准的 Cron 表达式更精确。
  • * 表示任意值,? 用于占位,表示“不指定”。
  • L 表示每月的最后一天或者每周的最后一天。
  • */n 表示每隔 n 个单位执行一次。
  • W 表示最接近的工作日,通常用于日期字段。

适用场景:

Quartz 的 Cron 表达式非常适合需要精确控制任务执行时间的场景,特别是在分布式系统中,可以确保任务按照严格的时间周期执行。

单机模式

只在项目启动时执行一次的任务

对于项目启动时只执行一次的任务,更推荐以实现 CommandLineRunner的类来开启子线程,在项目启动后,执行初始化数据等操作。这种方式更方便。

1
2
3
4
5
6
7
# 项目启动初始化配置
boot:
config:
# 配置文件属性(off会被springboot解析成false,on会被解析成true)
enable-init-database-table: on # 初始化数据表(若不存在则执行)
enable-init-database-data: on # 初始化数据库数据(若不存在则执行)
enable-first-start-clear-redis-catch: on # 清空Redis缓存
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
/**
* SpringBoot 副线程类
* 日期:2024/8/12
* 说明:此副线程一般用于以下场景
* - 数据初始化:在应用程序启动时加载一些初始数据到数据库中。
* - 发送通知:向用户或系统发送启动通知,例如发送邮件或消息队列中的消息。
* - 执行检查:进行应用程序启动前的检查,如数据库连接检查、文件系统检查等。
* - 启动日志:记录应用程序启动日志,提供启动时的系统状态信息。
* - 定时任务:启动一些定时任务,如清理旧数据、备份操作等。
*/
@Component
@Data
@ConfigurationProperties(prefix = "boot.config")
public class ApplicationInitializer implements CommandLineRunner {

private boolean enableInitDatabaseTable;
private boolean enableInitDatabaseData;
private boolean enableFirstStartClearRedisCatch;

/**
* 数据库初始化业务类
*/
@Resource
private DBInitializerService dbInitializerService;

/**
* SpringBoot 副线程 【在此项目中用于数据库初始化】
* @param args 命令参数
* @throws Exception 异常类
*/
@Override
public void run(String... args) throws Exception {
System.out.println("=================<初始化数据库>=================");
try {
if (enableInitDatabaseTable) {
dbInitializerService.initDBTable(); // 初始化数据库表结构
}else {
System.out.println("配置文件已关闭初始化数据库表。");
}
if (enableInitDatabaseData) {
dbInitializerService.initDBData(); // 初始化数据库数据
}else {
System.out.println("配置文件已关闭初始化数据库表数据。");
}
if (enableFirstStartClearRedisCatch) {
dbInitializerService.clearRedisCatch(); // 清空Redis缓存
}else {
System.out.println("配置文件已关闭清空Redis缓存。");
}

}catch (Exception e) {
System.out.println("初始化失败,系统终止!");
// 初始化失败,系统终止
System.exit(1);
}

System.out.println("初始化完成!");
System.out.println("==============================================");
}
}

这里也写一下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
/**
* 项目启动时执行一次的任务
* @author peter
* @date 2024/11/20
* @description TODO
*/
@Component
@RequiredArgsConstructor
public class InitDBTask implements Job {

public final DBInitializerService dbInitializerService;

@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("开始执行单次计划任务:数据库表,数据初始化和Redis缓存清理工作!");
try {
dbInitializerService.initDBTable();
dbInitializerService.initDBData();
dbInitializerService.clearRedisCatch();
}catch (Exception e){
System.out.println("初始化任务失败!开始停止项目!");
System.exit(0);
}

}
}

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

import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
* 用于首次启动时执行一次的简单任务触发器
* @author peter
* @date 2024/11/20
* @className SingleExecutionJobScheduler
* @packageName com.msrl.zhibo.task
* @description TODO
*/
@Component
public class SingleExecutionJobScheduler {

@Autowired
private Scheduler scheduler;

@PostConstruct
public void scheduleJob() throws SchedulerException {
JobDetail jobDetail = JobBuilder.newJob(InitDBTask.class) // 自定义的Job类
.withIdentity("startupJob", "DEFAULT")
.build();

Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("startupTrigger", "DEFAULT")
.startNow() // 项目启动后立即执行
.build();

scheduler.scheduleJob(jobDetail, trigger);
}
}

配置文件的方式

这种方式可以解决:如果每增加一个任务就要在配置类中添加一个调度器,这样比较繁琐,且不利于管理。

配置文件
1
2
3
4
5
6
7
# 定时任务配置
quartz:
tasks:
- jobName: "cleanMessageRecordTask"
jobGroup: "DEFAULT"
cronExpression: "0 0 0 1/2 * ? *" # 从每月1号开始,每两天执行一次,1、3、5...
jobClassName: "com.msrl.zhibo.task.cleanMessageRecordTask"
配置类
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
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

import javax.sql.DataSource;

// 这里的配置是为了能在任务类中使用spring注入功能,否则使用注入功能将报错
@Configuration
public class QuartzConfig {

@Bean
public SpringBeanJobFactory springBeanJobFactory(ApplicationContext applicationContext) {
SpringBeanJobFactory jobFactory = new SpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext); // 将 Spring 的 ApplicationContext 注入
return jobFactory;
}

@Bean
public SchedulerFactoryBean schedulerFactoryBean(SpringBeanJobFactory jobFactory, DataSource dataSource) {
SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
factoryBean.setJobFactory(jobFactory); // 设置自定义的 JobFactory,支持 Spring 注入
//factoryBean.setDataSource(dataSource); // 配置数据源(可选)
return factoryBean;
}
}
任务类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 过期消息清理任务【处理7天前的消息】
* @author peter
* @date 2024/11/20
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))// 此处的参数是兼容spring4的写法
public class cleanMessageRecordTask implements Job {

private final LiveChatRecordService liveChatRecordServiceImpl;

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

// 每次清理1000条符合条件的过期的7天前数据
liveChatRecordServiceImpl.batchRemove();
}
}

调度器
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
/**
* 通过配置文件动态加载定时任务的调度器
* @author peter
* @date 2024/11/20
* @className QuartzYamlScheduler
*/
@Service
@Data
@ConfigurationProperties(prefix = "quartz")
public class QuartzYamlScheduler {

@Resource // 因为从配置文件获取值,不适合再通过构造参数注入
private Scheduler scheduler;

private List<Map<String, String>> tasks;

public QuartzYamlScheduler() throws SchedulerException {
this.scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
}


// 加载配置文件中的任务
@PostConstruct
public void loadTasks() throws SchedulerException {
for (Map<String, String> task : tasks) {
JobDetail jobDetail = JobBuilder.newJob(getClass(task.get("jobClassName")))
.withIdentity(task.get("jobName"), task.get("jobGroup"))
.build();

Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(task.get("jobName") + "Trigger", task.get("jobGroup"))
.withSchedule(CronScheduleBuilder.cronSchedule(task.get("cronExpression")))
.build();

scheduler.scheduleJob(jobDetail, trigger);
}
}

private Class<? extends Job> getClass(String jobClassName) {
try {
return (Class<? extends Job>) Class.forName(jobClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Class not found: " + jobClassName);
}
}
}

这样就可以不用配置类了。

自定义存数据库的方式

注意:这里存到数据库的是方便查看的,这里重启不会恢复计划任务接着上次的执行,比如上次执行到9了,重启后,这次依然从头开始。若需要重启后恢复,需要用quartz专门的sql进行创建

配置类

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
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

import javax.sql.DataSource;

// 这里的配置是为了能在任务类中使用spring注入功能,否则使用注入功能将报错
@Configuration
public class QuartzConfig {

@Bean
public SpringBeanJobFactory springBeanJobFactory(ApplicationContext applicationContext) {
SpringBeanJobFactory jobFactory = new SpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext); // 将 Spring 的 ApplicationContext 注入
return jobFactory;
}

@Bean
public SchedulerFactoryBean schedulerFactoryBean(SpringBeanJobFactory jobFactory, DataSource dataSource) {
SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
factoryBean.setJobFactory(jobFactory); // 设置自定义的 JobFactory,支持 Spring 注入
//factoryBean.setDataSource(dataSource); // 配置数据源(可选)
return factoryBean;
}
}

实体类

quartz.java

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
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;

/**
* 定时任务表
* @author peter
* @date 2024/12/2
* @description 项目的定时任务信息
*/
@Data
@TableName(value = "quartz")
public class Quartz implements Serializable {

/**
* 定时任务表 > 编号
*/
@TableId(type = IdType.AUTO)
private Long quartzId;

/**
* 定时任务表 > 任务启用状态 【0 > 启用 || 1 > 停止】
*/
private Integer quartzStatus;

/**
* 定时任务表 > 任务名
*/
private String quartzJobName;

/**
* 定时任务表 > 任务组
*/
private String quartzJobGroup;

/**
* 定时任务表 > 任务描述
*/
private String quartzJobDescription;

/**
* 定时任务表 > 任务目标类
*/
private String quartzJobClass;

/**
* 定时任务表 > 触发器名
*/
private String quartzTriggerName;

/**
* 定时任务表 > 触发器分组
*/
private String quartzTriggerGroup;

/**
* 定时任务表 > 任务触发器描述
*/
private String quartzTriggerDescription;

/**
* 定时任务表 > CRON时间表达式
*/
private String quartzCronExpression;

private static final long serialVersionUID = 1L;

}

数据库创建语句

初始化mapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 创建定时任务表 -->
<update id="CreateQuartzTable">
CREATE TABLE IF NOT EXISTS `quartz`
(
`quartz_id` BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '编号',
`quartz_status` INTEGER DEFAULT 0 COMMENT '任务状态',
`quartz_job_name` VARCHAR(255) COMMENT '任务名称',
`quartz_job_group` VARCHAR(255) COMMENT '任务分组名',
`quartz_job_class` VARCHAR(255) COMMENT '目标任务类',
`quartz_job_description` VARCHAR(255) COMMENT '任务描述',
`quartz_trigger_name` VARCHAR(255) COMMENT '触发器名',
`quartz_trigger_group` VARCHAR(255) COMMENT '触发器分组',
`quartz_trigger_description` VARCHAR(255) COMMENT '任务描述',
`quartz_cron_expression` VARCHAR(255) COMMENT 'CRON时间表达式'
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '定时任务表';
</update>

初始化定时任务方法

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
import lombok.Data;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
* SpringBoot 副线程类
* 说明:此副线程一般用于以下场景
* - 数据初始化:在应用程序启动时加载一些初始数据到数据库中。
* - 发送通知:向用户或系统发送启动通知,例如发送邮件或消息队列中的消息。
* - 执行检查:进行应用程序启动前的检查,如数据库连接检查、文件系统检查等。
* - 启动日志:记录应用程序启动日志,提供启动时的系统状态信息。
* - 定时任务:启动一些定时任务,如清理旧数据、备份操作等。
*/
@Component
public class ApplicationInitializer implements CommandLineRunner {

/**
* SpringBoot 副线程 【在此项目中用于数据库初始化】
* @param args 命令参数
* @throws Exception 异常类
*/
@Override
public void run(String... args) throws Exception {
// TODO: 这里编写定时任务的初始化逻辑

}
}

定时任务方法

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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* 定时任务实现类
* @author peter
* @date 2024/12/2
* @description TODO
*/
@Service
@Data
@ConfigurationProperties(prefix = "quartz")
public class QuartzServiceImpl implements QuartzService {

private static final int OPEN = 0;
private static final int PAUSED = 1;

private List<Quartz> tasks;
@Resource
private QuartzMapper quartzMapper;
@Resource
private Scheduler scheduler;

/**
* 初始化定时任务
* @author peter
* @date 2024/12/2
* @className QuartzServiceImpl
* @packageName com.msrl.zhibo.service.impl
*/
@Override
public boolean init(){
// 从数据库获取定时任务
List<Quartz> quartzList = quartzMapper.selectList(null);
// 若定时任务为空则终止初始化程序
if (quartzList == null || quartzList.isEmpty()){
if (tasks.isEmpty()){
return false;
}else {
int num = 0;
try {
for (Quartz quartz : tasks) {
int insert = quartzMapper.insert(quartz);
if (insert > 0) {
++num;
}
}
FormatPrintUtil.Info("初始化定时任务,应添加定时任务"+tasks.size()+"个,实际添加定时任务"+num+"个,未添加任务"+(tasks.size()-num)+"个");
}catch (Exception e){
FormatPrintUtil.Error("初始化默认定时任务异常!");
return false;
}
}
quartzList = quartzMapper.selectList(null);
}

// 遍历获取到的所有任务
for (Quartz quartz : quartzList) {
try {
JobKey jobKey = JobKey.jobKey(quartz.getQuartzJobName(), quartz.getQuartzJobGroup());
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
// 若任务不存在,则正常添加任务
if (jobDetail==null) {
jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(quartz.getQuartzJobClass())).withIdentity(jobKey).withDescription(quartz.getQuartzJobDescription()).build();
}
TriggerKey triggerKey = TriggerKey.triggerKey(quartz.getQuartzTriggerName(), quartz.getQuartzTriggerGroup());
// 通过键名获取触发器
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (trigger==null){
// 触发器名,触发器组
TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger().withIdentity(quartz.getQuartzTriggerName(), quartz.getQuartzTriggerGroup());
//triggerBuilder.startNow();
// 触发时间设定
triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(quartz.getQuartzCronExpression()));
// 创建Trigger对象
trigger = (CronTrigger) triggerBuilder.build();

List<? extends Trigger> triggersOfJob = scheduler.getTriggersOfJob(jobDetail.getKey());
scheduler.deleteJob(jobDetail.getKey());


// 将任务与触发器关联
scheduler.scheduleJob(jobDetail,trigger);
for (Trigger trigger1 : triggersOfJob) {
scheduler.scheduleJob(trigger1);
}


// 若任务为未启用状态,则暂停触发器执行
if (quartz.getQuartzStatus()==PAUSED){
scheduler.pauseTrigger(trigger.getKey());
}
// // 将任务添加到调度器
// scheduler.addJob(jobDetail, false); // false表示不会覆盖已有任务
scheduler.start();
}else {
// 若触发器不为空,则该触发器不能使用,创建失败!
return false;
}

} catch (SchedulerException | ClassNotFoundException e) {
FormatPrintUtil.Info("任务初始化失败!"+e.getMessage());
return false;
} finally {
FormatPrintUtil.Info("初始化定时任务:"+quartz.getQuartzJobName());
}
}
return true;
}

/**
* 计划任务状态【总体】
* @author peter
* @date 2024/12/2
* @className QuartzServiceImpl
* @packageName com.msrl.zhibo.service.impl
*/
@Override
public boolean status(String status) {
try {
if (status.equals(String.valueOf(PAUSED))){
// 暂停所有计划任务
scheduler.standby();
return false;
}else {
// 启动所有任务
scheduler.start();
return true;
}
} catch (SchedulerException e) {
throw new RuntimeException("调度器异常!");
}
}

/**
* 计划任务状态【单体】
* @author peter
* @date 2024/12/2
* @className QuartzServiceImpl
* @packageName com.msrl.zhibo.service.impl
*/
@Override
public boolean singleJob(String id,String status) {

try {
Quartz quartz = quartzMapper.selectById(id);
if (quartz==null){
return false;
}
TriggerKey triggerKey = TriggerKey.triggerKey(quartz.getQuartzTriggerName(), quartz.getQuartzTriggerGroup());
// 通过键名获取触发器
CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 若触发器为空,或者触发器的时间表达式与数据库记录的不同,则终止方法
if(oldTrigger == null || !oldTrigger.getCronExpression().equalsIgnoreCase(quartz.getQuartzCronExpression())){
return false;
}
// 若为1,恢复当前触发器
if (status.equals(String.valueOf(PAUSED))){
scheduler.resumeTrigger(triggerKey);
}else { // 暂停当前任务触发器
scheduler.pauseTrigger(triggerKey);
}
} catch (SchedulerException e) {
FormatPrintUtil.Error("任务执行失败!"+e.getMessage());
return false;
}
return true;
}

/**
* 计划任务状态【任务组】
* @author peter
* @date 2024/12/2
* @className QuartzServiceImpl
* @packageName com.msrl.zhibo.service.impl
*/
@Override
public boolean jobGroup(String jobGroup,String status) {
// 根据任务组名称获取当前任务组下所有匹配到的任务
GroupMatcher<JobKey> groupMatcher = GroupMatcher.jobGroupEquals(jobGroup);
try {
if (status.equals(String.valueOf(PAUSED))){
scheduler.pauseJobs(groupMatcher);
System.out.println("任务组 " + jobGroup + " 中的所有任务已暂停");
}else {
scheduler.resumeJobs(groupMatcher);
System.out.println("任务组 " + jobGroup + " 中的所有任务已恢复");
}
} catch (SchedulerException e) {
throw new RuntimeException(e);
}
return true;
}


/**
* 执行一次任务
* @author peter
* @date 2024/12/2
* @className QuartzServiceImpl
* @packageName com.msrl.zhibo.service.impl
*/
@Override
public boolean once(String id){
Quartz quartz = quartzMapper.selectById(id);
if (quartz==null){
return false;
}
JobKey jobKey = JobKey.jobKey(quartz.getQuartzJobName(), quartz.getQuartzJobGroup());
// 立刻执行一次任务
try {
scheduler.triggerJob(jobKey);
} catch (SchedulerException e) {
throw new RuntimeException(e);
}
return true;
}

/**
* 查询所有计划任务
* @author peter
* @date 2024/12/2
* @className QuartzServiceImpl
* @packageName com.msrl.zhibo.service.impl
*/
@Override
public Page<Quartz> query(Page<Quartz> page){
quartzMapper.selectPage(page,null);
return page;
}

/**
* 新增定时任务
* @author peter
* @date 2024/12/2
* @className QuartzServiceImpl
* @packageName com.msrl.zhibo.service.impl
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean add(Quartz quartz){
try {
int insert = quartzMapper.insert(quartz);
if (insert == 0){
return false;
}
// 任务名,任务组,任务类
JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(quartz.getQuartzJobClass()))
.withIdentity(quartz.getQuartzJobName(), quartz.getQuartzJobGroup())
.withDescription(quartz.getQuartzJobDescription())
.build();
// 触发器名,触发器组
TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger().withIdentity(quartz.getQuartzTriggerName(), quartz.getQuartzTriggerGroup()).withDescription(quartz.getQuartzTriggerDescription());
//triggerBuilder.startNow();
// 触发时间设定
triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(quartz.getQuartzCronExpression()));
// 创建Trigger对象
CronTrigger trigger = (CronTrigger) triggerBuilder.build();
// 将任务与触发器关联
scheduler.scheduleJob(jobDetail, trigger);
// 若任务为未启用状态,则暂停触发器执行
if (quartz.getQuartzStatus()==1){
scheduler.pauseTrigger(trigger.getKey());
}
// 启动定时任务
scheduler.start();

} catch (SchedulerException e) {
throw new RuntimeException("调度器异常!");
} catch (ClassNotFoundException e) {
throw new RuntimeException("未找到目标任务类!");

} finally {
System.out.println("......新增定时任务......");
}
return true;
}

/**
* 更新定时任务
* @author peter
* @date 2024/12/2
* @className QuartzServiceImpl
* @packageName com.msrl.zhibo.service.impl
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean update(Quartz quartz) {
// 获取数据库中的定时任务信息
Quartz quartzDBData = quartzMapper.selectById(quartz.getQuartzId());

try {
// 获取要修改的任务触发器键名
TriggerKey triggerKey = TriggerKey.triggerKey(quartz.getQuartzTriggerName(), quartz.getQuartzTriggerGroup());
// 通过键名获取触发器
CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 若触发器为空,或者触发器的时间表达式与数据库记录的不同,则终止方法
if(oldTrigger == null || !oldTrigger.getCronExpression().equalsIgnoreCase(quartzDBData.getQuartzCronExpression())){
return false;
}
JobKey jobKey = JobKey.jobKey(quartz.getQuartzJobName(), quartz.getQuartzJobGroup());
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
// 若不存在此计划,终止
if (jobDetail == null) {
return false;
}

JobDetail updatedJobDetail = jobDetail.getJobBuilder()
.withDescription(quartz.getQuartzJobDescription()) // 设置新的描述
.build();

// 更新任务
scheduler.addJob(updatedJobDetail, true); // true 表示替换现有任务

// 触发器名,触发器组
TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger()
.withIdentity(quartz.getQuartzTriggerName(),quartz.getQuartzTriggerGroup())
.withDescription(quartz.getQuartzTriggerDescription());
// 若修改的执行状态为执行一次
// if (quartz.getQuartzStatus()==1){
// triggerBuilder.startNow();
// }

// 设置任务触发的时间表达式
triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(quartz.getQuartzCronExpression()));

// 创建Trigger对象
CronTrigger newTrigger = (CronTrigger) triggerBuilder.build();
// 更新
scheduler.rescheduleJob(triggerKey, newTrigger);
if (quartz.getQuartzStatus()==1){
scheduler.pauseTrigger(triggerKey);
}
// 虽然这里只需要在初始化时执行一次就可以了,但为了防止初始化无任务时没有启动定时任务的情况。
scheduler.start();
int i = quartzMapper.updateById(quartz);
if (i==0){
return false;
}
}catch (SchedulerException e){
throw new RuntimeException("调度器异常!");
}
return true;
}

/**
* 移除一个计划任务
* @author peter
* @date 2024/12/2
* @className QuartzServiceImpl
* @packageName com.msrl.zhibo.service.impl
*/
@Override
@Transactional
public boolean delete(String id) {
// 查询是否存在
Quartz quartz = quartzMapper.selectById(id);
if (quartz==null){
return false;
}
try {
JobKey jobKey = JobKey.jobKey(quartz.getQuartzJobName(), quartz.getQuartzJobGroup());
List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
// 若触发器为空,则无需删除
if (triggers == null || triggers.isEmpty()){
return true;
}else if(triggers.size() == 1){ // 若触发器只有一个,则可以删除触发器和任务
// 删除任务及其所有触发器
boolean b = scheduler.deleteJob(JobKey.jobKey(quartz.getQuartzJobName(), quartz.getQuartzJobGroup()));
// 删除计划任务
int i = quartzMapper.deleteById(quartz.getQuartzId());
// 若有一个删除失败,那都是失败的
if (!b || i==0){
throw new RuntimeException("删除计划任务失败!");
}
return true;
}else {
// 移除触发器
boolean b = scheduler.unscheduleJob(TriggerKey.triggerKey(quartz.getQuartzTriggerName(), quartz.getQuartzTriggerGroup()));

// 删除计划任务
int i = quartzMapper.deleteById(quartz.getQuartzId());
if (!b || i==0){
throw new RuntimeException("删除计划任务失败!");
}
return true;
}

} catch (SchedulerException e) {
e.printStackTrace();
System.out.println("删除失败!");
return false;
}finally {
System.out.println("......移除一个定时任务......");
}
}

}

用官方存数据库的方式

此种方式需要用官方的sql文件导入数据库进行初始化。这种方式在项目运行期间会自行保存到数据库。并在每次项目启动时读取定时任务。


Quartz - 定时任务框架
https://superlovelace.top/2024/11/12/Quartz定时任务/
作者
棱境
发布于
2024年11月12日
更新于
2025年2月18日
许可协议