Quartz学习
# Quartz 学习
代码仓库地址:
# 基本介绍
Quartz 是一个开源的作业调度框架,它完全由 Java 写成。
Quartz 自带了集群方案。它通过将作业信息存储到关系数据库中,并使用关系数据库的行锁来实现执行作业的竞争,从而保证多个进程下,同一个任务在相同时刻,不能重复执行。
在 Quartz 体系结构中,有三个组件非常重要:
- Scheduler :调度器
- Trigger :触发器
- Job :任务
Quartz 分成【单机模式】和【集群模式】。在生产环境下,一定一定一定要使用 Quartz 的集群模式,保证定时任务的高可用。
# JobStore
的存储方式
有两种:
RAMJobStore
:将 scheduler 存储在内存中,但是重启服务器信息会丢失。JDBCJobStore
:将 scheduler 存储在数据库中。
类型 | 优点 | 缺点 |
---|---|---|
RAMJobStore | 不要外部数据库,配置容易,运行速度快 | 因为调度程序信息是存储在被分配给 JVM 的内存里面,所以,当应用程序停止运行时,所有调度信息将被丢失。另外因为存储到 JVM 内存里面,所以可以存储多少个 Job 和 Trigger 将会受到限制 |
JDBC 作业存储 | 支持集群,因为所有的任务信息都会保存到数据库中,可以控制事物,还有就是如果应用服务器关闭或者重启,任务信息都不会丢失,并且可以恢复因服务器关闭或者重启而导致执行失败的任务 | 运行速度的快慢取决与连接数据库的快慢 |
# Quartz 单机入门示例
# 引入依赖
<dependencies>
<!-- 实现对 Spring MVC 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 实现对 Quartz 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 应用配置文件
spring:
application:
name:
task-quartz-memory
# Quartz 的配置,对应 QuartzProperties 配置类
quartz:
job-store-type: memory # Job 存储器类型。默认为 memory 表示内存,可选 jdbc 使用数据库。
auto-startup: true # Quartz 是否自动启动
startup-delay: 0 # 延迟 N 秒启动
wait-for-jobs-to-complete-on-shutdown: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true
overwrite-existing-jobs: false # 是否覆盖已有 Job 的配置
properties: # 添加 Quartz Scheduler 附加属性,更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 文档
org:
quartz:
threadPool:
threadCount: 25 # 线程池大小。默认为 10 。
threadPriority: 5 # 线程优先级
class: org.quartz.simpl.SimpleThreadPool # 线程池类型
# jdbc: # 这里暂时不说明,使用 JDBC 的 JobStore 的时候,才需要配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 创建任务类
/**
* 每次 DemoJob01 都会被 Quartz 创建出一个新的 Job 对象,执行任务。
*
* @author chenmeng
*/
public class DemoJob01 extends QuartzJobBean {
private final Logger logger = LoggerFactory.getLogger(getClass());
// 计数器
private final AtomicInteger counts = new AtomicInteger();
@Resource
private DemoService demoService;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
// counts.incrementAndGet() 的值一直为 1,说明每次 DemoJob01 都是新创建的。
logger.info("[executeInternal][定时第 ({}) 次执行, demoService 为 ({})]", counts.incrementAndGet(),
demoService);
}
}
/**
* @author chenmeng
*/
public class DemoJob02 extends QuartzJobBean {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
logger.info("[DemoJob02-executeInternal][我每第10秒执行一次]");
}
}
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
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
# 新增配置类
@Configuration
public class ScheduleConfiguration {
public static class DemoJob01Configuration {
/**
* 创建 DemoJob01 的 JobDetail Bean 对象
*
* @return JobDetail
*/
@Bean
public JobDetail demoJob01() {
return JobBuilder.newJob(DemoJob01.class)
.withIdentity("demoJob01") // 名字为 demoJob01
.storeDurably() // 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时,还没 Trigger 指向它,所以需要设置为 true ,表示保留。
.build();
}
/**
* 创建 DemoJob01 的 Trigger Bean 对象。
* 其中,我们使用 SimpleScheduleBuilder 简单的调度计划的构造器,创建了每 5 秒执行一次,无限重复的调度计划
*
* @return Trigger
*/
@Bean
public Trigger demoJob01Trigger() {
// 简单的调度计划的构造器
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5) // 频率 - 每 5 秒。
.repeatForever(); // 次数 - 无限重复。
// Trigger 构造器
return TriggerBuilder.newTrigger()
.forJob(demoJob01()) // 对应 Job 为 demoJob01
.withIdentity("demoJob01Trigger") // 名字为 demoJob01Trigger
.withSchedule(scheduleBuilder) // 对应 Schedule 为 scheduleBuilder
.build();
}
}
public static class DemoJob02Configuration {
/**
* 创建 DemoJob02 的 JobDetail Bean 对象
*
* @return JobDetail
*/
@Bean
public JobDetail demoJob02() {
return JobBuilder.newJob(DemoJob02.class)
.withIdentity("demoJob02") // 名字为 demoJob02
.storeDurably() // 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时,还没 Trigger 指向它,所以需要设置为 true ,表示保留。
.build();
}
/**
* 创建 DemoJob02 的 Trigger Bean 对象。
* 其中,我们使用 CronScheduleBuilder 基于 Quartz Cron 表达式的调度计划的构造器,
* 创建了每第 10 秒执行一次的调度计划。
*
* @return Trigger
*/
@Bean
public Trigger demoJob02Trigger() {
// 基于 Quartz Cron 表达式的调度计划的构造器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ? *");
// Trigger 构造器
return TriggerBuilder.newTrigger()
.forJob(demoJob02()) // 对应 Job 为 demoJob02
.withIdentity("demoJob02Trigger") // 名字为 demoJob02Trigger
.withSchedule(scheduleBuilder) // 对应 Schedule 为 scheduleBuilder
.build();
}
}
}
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
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
最后启动运行测试。
# Quartz 集群入门示例
# 引入依赖
<dependencies>
<!-- 实现对数据库连接池的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 本示例,我们使用 MySQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- 实现对 Spring MVC 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 实现对 Quartz 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
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
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
# 应用配置文件
spring:
application:
name:
task-quartz-jdbc
datasource:
user: # 这里主要目的是,为了模拟我们一般项目,使用到的业务数据库。
url: jdbc:mysql://127.0.0.1:3306/chenmeng_test?useSSL=false&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password:
quartz: # 这里主要目的是,Quartz 会使用单独的数据库。如果我们有多个项目需要使用到 Quartz 数据库的话,可以统一使用一个,但是要注意配置 spring.quartz.scheduler-name 设置不同的 Scheduler 名字,形成不同的 Quartz 集群。
url: jdbc:mysql://127.0.0.1:3306/chenmeng_quartz?useSSL=false&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password:
# Quartz 的配置,对应 QuartzProperties 配置类
quartz:
scheduler-name: clusteredScheduler # Scheduler 名字。默认为 schedulerName
job-store-type: jdbc # Job 存储器类型。默认为 memory 表示内存,可选 jdbc 使用数据库。
auto-startup: true # Quartz 是否自动启动
startup-delay: 0 # 延迟 N 秒启动
wait-for-jobs-to-complete-on-shutdown: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true
overwrite-existing-jobs: false # 是否覆盖已有 Job 的配置
properties: # 添加 Quartz Scheduler 附加属性,更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 文档
org:
quartz:
# JobStore 相关配置
jobStore:
# 数据源名称
dataSource: quartzDataSource # 使用的数据源
# Spring Boot 2.6.0+ 中,job-store-type: jdbc 会自动选择 JobStoreTX,无需显式指定 org.quartz.jobStore.class。
# 如果指定了,项目运行会报错。
# class: org.quartz.impl.jdbcjobstore.JobStoreTX # JobStore 实现类
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
tablePrefix: QRTZ_ # Quartz 表前缀
isClustered: true # 是集群模式
clusterCheckinInterval: 1000
useProperties: false
# 线程池相关配置
threadPool:
threadCount: 25 # 线程池大小。默认为 10 。
threadPriority: 5 # 线程优先级
class: org.quartz.simpl.SimpleThreadPool # 线程池类型
jdbc: # 使用 JDBC 的 JobStore 的时候,JDBC 的配置
initialize-schema: never # 是否自动使用 SQL 初始化 Quartz 表结构。这里设置成 never ,我们手动创建表结构。
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
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
注意事项:
- Spring Boot 2.6.0+ 中,
job-store-type: jdbc
会自动选择JobStoreTX
,无需显式指定org.quartz.jobStore.class
。 - 如果手动指定
class: org.quartz.impl.jdbcjobstore.JobStoreTX
,需要注意配置冲突,可能需要禁用 Spring Boot 的自动配置。 - 最佳实践是依赖 Spring Boot 的自动配置,减少手动干预。
# 初始化 Quartz 表结构
Quartz 下载:
在 Quartz Download (opens new window) 地址,下载对应版本的发布包。
- 解压后,我们可以在
src/org/quartz/impl/jdbcjobstore/
目录,看到各种数据库的 Quartz 表结构的初始化脚本。 - 这里,因为我们使用 MySQL,所以使用
tables_mysql_innodb.sql
脚本。
Quartz 表结构的说明:
(详细说明参见下面的 [表结构详解])
qrtz_job_details
:记录每个任务的详细信息。qrtz_triggers
:记录每个触发器的详细信息。qrtz_corn_triggers
:记录 cornTrigger 的信息。qrtz_simple_triggers
:存储SimpleTrigger
类型触发器的时间信息。qrtz_simprop_triggers
:记录带有简单属性的触发器信息(仅在需要时使用)。qrtz_blob_triggers
:存储复杂触发器(如可能包含序列化对象的触发器)。qrtz_calendars
:记录调度器使用的日历信息(用于排除任务触发时间,如节假日)。qrtz_fired_triggers
:记录每个正在执行的触发器。qrtz_paused_trigger_grps
:记录被暂停的触发器分组。qrtz_scheduler_state
:记录 调度器(每个机器节点)的生命状态。qrtz_locks
:记录程序的悲观锁(防止多个节点同时执行同一个定时任务)。
我们会发现,每个表都有一个
SCHED_NAME
字段,Quartz Scheduler 名字。这样就实现了每个 Quartz 集群,在数据层面的拆分。
# 数据源配置类
@Configuration
public class DataSourceConfiguration {
/**
* 创建 user 数据源的配置对象
*/
@Primary
@Bean(name = "userDataSourceProperties")
@ConfigurationProperties(prefix = "spring.datasource.user") // 读取 spring.datasource.user 配置到 DataSourceProperties 对象
public DataSourceProperties userDataSourceProperties() {
return new DataSourceProperties();
}
/**
* 创建 user 数据源
*/
@Primary
@Bean(name = "userDataSource")
@ConfigurationProperties(prefix = "spring.datasource.user.hikari")
// 读取 spring.datasource.user 配置到 HikariDataSource 对象
public DataSource userDataSource() {
// 获得 DataSourceProperties 对象
DataSourceProperties properties = this.userDataSourceProperties();
// 创建 HikariDataSource 对象
return createHikariDataSource(properties);
}
/**
* 创建 quartz 数据源的配置对象
*/
@Bean(name = "quartzDataSourceProperties")
@ConfigurationProperties(prefix = "spring.datasource.quartz")
// 读取 spring.datasource.quartz 配置到 DataSourceProperties 对象
public DataSourceProperties quartzDataSourceProperties() {
return new DataSourceProperties();
}
/**
* 创建 quartz 数据源
*/
@Bean(name = "quartzDataSource")
@ConfigurationProperties(prefix = "spring.datasource.quartz.hikari")
@QuartzDataSource
public DataSource quartzDataSource() {
// 获得 DataSourceProperties 对象
DataSourceProperties properties = this.quartzDataSourceProperties();
// 创建 HikariDataSource 对象
return createHikariDataSource(properties);
}
private static HikariDataSource createHikariDataSource(DataSourceProperties properties) {
// 创建 HikariDataSource 对象
HikariDataSource dataSource = properties.initializeDataSourceBuilder()
.type(HikariDataSource.class)
.build();
// 设置线程池名
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
return dataSource;
}
}
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
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
# 创建任务类
@DisallowConcurrentExecution // 保证相同 JobDetail 在多个 JVM 进程中,有且仅有一个节点在执行
public class DemoJob01 extends QuartzJobBean {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Resource
private DemoService demoService;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
logger.info("[executeInternal][我开始执行了, demoService 为 ({})]", demoService);
}
}
public class DemoJob02 extends QuartzJobBean {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
logger.info("[DemoJob02-executeInternal][我开始执行了]");
}
}
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
注意事项:
@DisallowConcurrentExecution
注解 不是以 Quartz Job 为维度,保证在多个 JVM 进程中,有且仅有一个节点在执行,而是以 JobDetail 为维度。- 虽然说,绝大多数情况下,我们会保证一个 Job 和 JobDetail 是一一对应。
- 所以,搞不清楚这个概念的胖友,最好搞清楚这个概念。实在有点懵逼,保证一个 Job 和 JobDetail 是一一对应就对了。
- JobDetail 的唯一标识是 JobKey (opens new window) ,使用
name
+group
两个属性。- 一般情况下,我们只需要设置
name
即可,而 Quartz 会默认group = DEFAULT
。 - 在 Quartz 中,相同 Scheduler 名字的节点,形成一个 Quartz 集群。
- 我们可以通过
spring.quartz.scheduler-name
配置项,设置 Scheduler 的名字。
- 一般情况下,我们只需要设置
- 通过在 Job 实现类上添加
@DisallowConcurrentExecution
注解,实现在相同的 Quartz Scheduler 集群中,相同 JobKey 的 JobDetail,保证在多个 JVM 进程中,有且仅有一个节点在执行。
# 定时任务配置
# Bean 自动设置
/**
* Bean 自动设置
*
* @author chenmeng
*/
@Configuration
public class ScheduleConfiguration {
public static class DemoJob01Configuration {
/**
* 创建 DemoJob01 的 JobDetail Bean 对象
*
* @return JobDetail
*/
@Bean
public JobDetail demoJob01() {
return JobBuilder.newJob(DemoJob01.class)
.withIdentity("demoJob01") // 名字为 demoJob01
.storeDurably() // 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时,还没 Trigger 指向它,所以需要设置为 true ,表示保留。
.build();
}
/**
* 创建 DemoJob01 的 Trigger Bean 对象。
* 其中,我们使用 SimpleScheduleBuilder 简单的调度计划的构造器,创建了每 5 秒执行一次,无限重复的调度计划
*
* @return Trigger
*/
@Bean
public Trigger demoJob01Trigger() {
// 简单的调度计划的构造器
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5) // 频率 - 每 5 秒。
.repeatForever(); // 次数 - 无限重复。
// Trigger 构造器
return TriggerBuilder.newTrigger()
.forJob(demoJob01()) // 对应 Job 为 demoJob01
.withIdentity("demoJob01Trigger") // 名字为 demoJob01Trigger
.withSchedule(scheduleBuilder) // 对应 Schedule 为 scheduleBuilder
.build();
}
}
public static class DemoJob02Configuration {
/**
* 创建 DemoJob02 的 JobDetail Bean 对象
*
* @return JobDetail
*/
@Bean
public JobDetail demoJob02() {
return JobBuilder.newJob(DemoJob02.class)
.withIdentity("demoJob02") // 名字为 demoJob02
.storeDurably() // 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时,还没 Trigger 指向它,所以需要设置为 true ,表示保留。
.build();
}
/**
* 创建 DemoJob02 的 Trigger Bean 对象。
* 其中,我们使用 CronScheduleBuilder 基于 Quartz Cron 表达式的调度计划的构造器,
* 创建了每第 10 秒执行一次的调度计划。
*
* @return Trigger
*/
@Bean
public Trigger demoJob02Trigger() {
// 基于 Quartz Cron 表达式的调度计划的构造器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ? *");
// Trigger 构造器
return TriggerBuilder.newTrigger()
.forJob(demoJob02()) // 对应 Job 为 demoJob02
.withIdentity("demoJob02Trigger") // 名字为 demoJob02Trigger
.withSchedule(scheduleBuilder) // 对应 Schedule 为 scheduleBuilder
.build();
}
}
}
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
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
# Schedule 手动设置
/**
* Scheduler 手动设置
* 执行之前,需要确保项目中只有一个 @SpringBootApplication 注解 或者 指定扫描范围
*
* @author chenmeng
*/
@SpringBootTest(classes = QuartzJdbcApplication.class)
public class QuartzSchedulerTest {
@Autowired
private Scheduler scheduler;
@Test
public void addDemoJob01Config() throws SchedulerException {
// 创建 JobDetail
JobDetail jobDetail = JobBuilder.newJob(DemoJob01.class)
.withIdentity("demoJob01") // 名字为 demoJob01
.storeDurably() // 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时,还没 Trigger 指向它,所以需要设置为 true ,表示保留。
.build();
// 创建 Trigger
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5) // 频率。
.repeatForever(); // 次数。
Trigger trigger = TriggerBuilder.newTrigger()
.forJob(jobDetail) // 对应 Job 为 demoJob01
.withIdentity("demoJob01Trigger") // 名字为 demoJob01Trigger
.withSchedule(scheduleBuilder) // 对应 Schedule 为 scheduleBuilder
.build();
// 添加调度任务(将 JobDetail 和 Trigger 持久化到数据库)
scheduler.scheduleJob(jobDetail, trigger);
}
@Test
public void addDemoJob02Config() throws SchedulerException {
// 创建 JobDetail
JobDetail jobDetail = JobBuilder.newJob(DemoJob02.class)
.withIdentity("demoJob02") // 名字为 demoJob02
.storeDurably() // 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时,还没 Trigger 指向它,所以需要设置为 true ,表示保留。
.build();
// 创建 Trigger
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ? *");
Trigger trigger = TriggerBuilder.newTrigger()
.forJob(jobDetail) // 对应 Job 为 demoJob01
.withIdentity("demoJob02Trigger") // 名字为 demoJob01Trigger
.withSchedule(scheduleBuilder) // 对应 Schedule 为 scheduleBuilder
.build();
// 添加调度任务(将 JobDetail 和 Trigger 持久化到数据库)
scheduler.scheduleJob(jobDetail, trigger);
// 如果想要覆盖数据库中的 Quartz 定时任务的配置,可以调用此方法,传入 replace = true 进行覆盖配置。
// scheduler.scheduleJob(jobDetail, Sets.newSet(trigger), true);
}
}
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
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
# 启动测试
第一个服务:
@SpringBootApplication
public class QuartzJdbcApplication {
public static void main(String[] args) {
SpringApplication.run(QuartzJdbcApplication.class, args);
}
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
第二个服务:
/**
* 测试集群下的运行情况
*
* @author chenmeng
*/
@SpringBootApplication
public class QuartzJdbcApplication02 {
public static void main(String[] args) {
// 设置 Tomcat 随机端口
System.setProperty("server.port", "0");
// 启动 Spring Boot 应用
SpringApplication.run(QuartzJdbcApplication02.class, args);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Bug 记录
异常信息:
# 找不到数据源
Failed to obtain DB connection from data source 'quartzDataSource': java.sql.SQLException: There is no DataSource named 'quartzDataSource'
1
2
2
解决方案:
配置文件:
- 移除或注释掉
class: org.quartz.impl.jdbcjobstore.JobStoreTX
配置项
原因分析
为什么注释掉
class: org.quartz.impl.jdbcjobstore.JobStoreTX
能解决问题?
在 Spring Boot 2.6.0 及以上版本中,Quartz 的配置机制进行了增强,尤其是对于 JobStore
的自动配置:
- Spring Boot 自动管理
JobStore
类型:- Spring Boot 会根据
spring.quartz.job-store-type
属性自动推断JobStore
的实现类:memory
:使用内存存储(RAMJobStore
)。jdbc
:使用数据库存储(JobStoreTX
)。
- 如果你显式指定了
org.quartz.jobStore.class
,则会覆盖 Spring Boot 的默认行为,可能导致配置不兼容或冲突。
- Spring Boot 会根据
- Spring Boot Quartz Starter 的改进:
- Spring Boot 的
spring-boot-starter-quartz
在 2.6.0+ 中已经内置了对JobStoreTX
的支持, - 只需要正确配置
job-store-type: jdbc
,无需手动设置org.quartz.jobStore.class
。
- Spring Boot 的
- 手动配置
class: org.quartz.impl.jdbcjobstore.JobStoreTX
会冲突:- 如果手动配置了
org.quartz.jobStore.class
,Spring Boot 的自动配置逻辑可能会重复设置JobStore
,从而引发问题,例如无法正确初始化数据源或JobStore
。
- 如果手动配置了
# 表结构详解
以下是 Quartz 2.3.0 官方脚本中 11 张表的说明及其功能描述:
# 1、qrtz_job_details
(QRTZ_JOB_DETAILS
)
- 功能:记录每个定时任务的详细信息。
- 主要字段:
JOB_NAME
和JOB_GROUP
:任务名称和任务分组,联合主键。DESCRIPTION
:任务的描述信息。JOB_CLASS_NAME
:执行任务的类的全限定名。IS_DURABLE
:是否是持久任务(即任务是否在没有触发器时保留)。JOB_DATA
:任务附加的键值对数据。
# 2、qrtz_triggers
(QRTZ_TRIGGERS
)
- 功能:记录每个触发器的详细信息。
- 主要字段:
TRIGGER_NAME
和TRIGGER_GROUP
:触发器名称和分组,联合主键。JOB_NAME
和JOB_GROUP
:关联的任务名称和分组。TRIGGER_STATE
:触发器当前的状态(如等待、暂停、阻塞等)。TRIGGER_TYPE
:触发器的类型(如 Cron、Simple、Blob 等)。START_TIME
和END_TIME
:触发器的起始时间和结束时间。MISFIRE_INSTR
:触发器的误操作策略。
# 3、qrtz_corn_triggers
(QRTZ_CRON_TRIGGERS
)
- 功能:存储
CronTrigger
类型触发器的时间表达式。 - 主要字段:
TRIGGER_NAME
和TRIGGER_GROUP
:触发器名称和分组(外键关联QRTZ_TRIGGERS
表)。CRON_EXPRESSION
:Cron 表达式。TIME_ZONE_ID
:时区 ID。
# 4、qrtz_simple_triggers
(QRTZ_SIMPLE_TRIGGERS
)
- 功能:存储
SimpleTrigger
类型触发器的时间信息。 - 主要字段:
TRIGGER_NAME
和TRIGGER_GROUP
:触发器名称和分组(外键关联QRTZ_TRIGGERS
表)。REPEAT_COUNT
:触发次数。REPEAT_INTERVAL
:触发间隔。TIMES_TRIGGERED
:已触发次数。
# 5、qrtz_simprop_triggers
(QRTZ_SIMPROP_TRIGGERS
)
- 功能:记录带有简单属性的触发器信息(仅在需要时使用)。
- 主要字段:
TRIGGER_NAME
和TRIGGER_GROUP
:触发器名称和分组(外键关联QRTZ_TRIGGERS
表)。STR_PROP_1
~STR_PROP_4
:自定义字符串属性。INT_PROP_1
~INT_PROP_2
:自定义整数属性。DEC_PROP_1
~DEC_PROP_2
:自定义小数属性。BOOL_PROP_1
:自定义布尔属性。
# 6、qrtz_blob_triggers
(QRTZ_BLOB_TRIGGERS
)
- 功能:存储复杂触发器(如可能包含序列化对象的触发器)。
- 主要字段:
TRIGGER_NAME
和TRIGGER_GROUP
:触发器名称和分组(外键关联QRTZ_TRIGGERS
表)。BLOB_DATA
:二进制大对象数据。
# 7、qrtz_calendars
(QRTZ_CALENDARS
)
- 功能:记录调度器使用的日历信息(用于排除任务触发时间,如节假日)。
- 主要字段:
CALENDAR_NAME
:日历名称。CALENDAR
:序列化后的日历数据。
# 8、qrtz_fired_triggers
(QRTZ_FIRED_TRIGGERS
)
- 功能:记录每个正在执行的触发器实例。
- 主要字段:
ENTRY_ID
:执行条目的唯一标识。TRIGGER_NAME
和TRIGGER_GROUP
:触发器名称和分组。INSTANCE_NAME
:触发器所属的调度器实例。FIRED_TIME
:触发时间。PRIORITY
:触发器优先级。
# 9、qrtz_paused_trigger_grps
(QRTZ_PAUSED_TRIGGER_GRPS
)
- 功能:记录被暂停的触发器分组。
- 主要字段:
TRIGGER_GROUP
:被暂停的触发器分组名称。
# 10、qrtz_scheduler_state
(QRTZ_SCHEDULER_STATE
)
- 功能:记录调度器(每个机器节点)的运行状态。
- 主要字段:
INSTANCE_NAME
:调度器实例的名称。LAST_CHECKIN_TIME
:调度器最后一次心跳的时间。CHECKIN_INTERVAL
:心跳间隔。
# 11、qrtz_locks
(QRTZ_LOCKS
)
- 功能:记录调度器的全局锁(用于悲观锁,防止多节点竞争执行同一个任务)。
- 主要字段:
SCHED_NAME
:调度器名称。LOCK_NAME
:锁的名称。
# 表之间的关系
QRTZ_JOB_DETAILS
是核心任务表,记录任务基本信息。QRTZ_TRIGGERS
与任务相关联,存储触发器的基本信息。QRTZ_CRON_TRIGGERS
、QRTZ_SIMPLE_TRIGGERS
、QRTZ_BLOB_TRIGGERS
等扩展触发器表,存储不同类型触发器的详细信息。QRTZ_SCHEDULER_STATE
和QRTZ_LOCKS
用于调度器的分布式协调。
这 11 张表共同支撑了 Quartz 的任务调度功能,既支持单机调度,也支持分布式集群环境。
# 学习参考
上次更新: 2025/1/7 17:52:25