Springboot 使用Quartz定时器及持久化
导包
<!-- 实现对 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>
创建数据库
sql脚本目录:org\quartz-scheduler\quartz\2.3.2\quartz-2.3.2.jar!\org\quartz\impl\jdbcjobstore\tables_mysql_innodb.sql
DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;
CREATE TABLE QRTZ_JOB_DETAILS
(
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE VARCHAR(1) NOT NULL,
IS_NONCONCURRENT VARCHAR(1) NOT NULL,
IS_UPDATE_DATA VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);
CREATE TABLE QRTZ_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT(13) NULL,
PREV_FIRE_TIME BIGINT(13) NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT(13) NOT NULL,
END_TIME BIGINT(13) NULL,
CALENDAR_NAME VARCHAR(200) NULL,
MISFIRE_INSTR SMALLINT(2) NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
);
CREATE TABLE QRTZ_SIMPLE_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
REPEAT_COUNT BIGINT(7) NOT NULL,
REPEAT_INTERVAL BIGINT(12) NOT NULL,
TIMES_TRIGGERED BIGINT(10) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_CRON_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CRON_EXPRESSION VARCHAR(200) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_SIMPROP_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
STR_PROP_1 VARCHAR(512) NULL,
STR_PROP_2 VARCHAR(512) NULL,
STR_PROP_3 VARCHAR(512) NULL,
INT_PROP_1 INT NULL,
INT_PROP_2 INT NULL,
LONG_PROP_1 BIGINT NULL,
LONG_PROP_2 BIGINT NULL,
DEC_PROP_1 NUMERIC(13,4) NULL,
DEC_PROP_2 NUMERIC(13,4) NULL,
BOOL_PROP_1 VARCHAR(1) NULL,
BOOL_PROP_2 VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_BLOB_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_CALENDARS
(
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_FIRED_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
FIRED_TIME BIGINT(13) NOT NULL,
SCHED_TIME BIGINT(13) NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(200) NULL,
JOB_GROUP VARCHAR(200) NULL,
IS_NONCONCURRENT VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);
CREATE TABLE QRTZ_SCHEDULER_STATE
(
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);
CREATE TABLE QRTZ_LOCKS
(
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);
commit;
表信息
表名 | 信息 |
---|---|
qrtz_blob_triggers | blog类型存储triggers |
qrtz_calendars | 以blog类型存储Calendar信息 |
qrtz_cron_triggers | 存储cron trigger信息 |
qrtz_fired_triggers | 存储已触发的trigger相关信息 |
qrtz_job_details | 存储每一个已配置的job details |
qrtz_locks | 存储悲观锁的信息 |
qrtz_paused_trigger_grps | 存储已暂停的trigger组信息 |
qrtz_scheduler_state | 存储Scheduler状态信息 |
qrtz_simple_triggers | 存储simple trigger信息 |
qrtz_simprop_triggers | 存储其他几种trigger信息 |
qrtz_triggers | 存储已配置的trigger信息 |
配置
在 springboot 启动类 Application 里面加上注解 @EnableScheduling 使用定时器
spring:
datasource:
quartz:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/quartz?serverTimezone=GMT%2B8
username: root
password: 123456
quartz:
job-store-type: jdbc # 使用数据库存储,默认使用内存
scheduler-name: scheduler # 相同 Scheduler 名字的节点,形成一个 Quartz 集群
wait-for-jobs-to-complete-on-shutdown: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true
jdbc:
initialize-schema: never # 是否自动使用 SQL 初始化 Quartz 表结构。这里设置成 never ,我们手动创建表结构。
properties:
org:
quartz:
# JobStore 相关配置
jobStore:
dataSource: quartzDataSource # 使用的数据源
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 # 线程池类型
代码
创建需要执行的任务
package com.quartz.job;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Slf4j
public class ExecJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
String now = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now());
log.info("当前的时间: " + now+"执行了");
}
}
添加定时任务
/**
* 定时器
*/
@Resource
private Scheduler scheduler;
// resource 是实体,设置定时任务信息
// 任务详细信息
JobDetail jobDetail = JobBuilder.newJob(ExecJob.class)
.withIdentity(resource.getId()) // 设置id
.storeDurably()
.build();
// 任务执行规则
CronScheduleBuilder scheduleBuilder =
CronScheduleBuilder.cronSchedule(resource.getCron()); // 设置cron表达式
// 创建任务触发器
Trigger trigger = TriggerBuilder.newTrigger()
.forJob(jobDetail)
.withIdentity(resource.getId())
.withSchedule(scheduleBuilder)
.startNow() //立即執行一次任務
.build();
Set<Trigger> set = new HashSet<>();
set.add(trigger);
// boolean replace 表示启动时对数据库中的quartz的任务进行覆盖。
scheduler.scheduleJob(jobDetail, set, true);
删除定时任务
/**
* 定时器
*/
@Resource
private Scheduler scheduler;
JobKey jobKey=new JobKey(id);
if(scheduler.checkExists(jobKey)){
// 删除一个定时任务,同时也会将于该jobDetail关联的trigger一并删除
scheduler.deleteJob(jobKey);
}
Scheduler 接口里面提供了很多的操作方法
注解使用定时任务
@Component
@Slf4j
public class MachineRoomStateJob {
/**
* 每五分钟执行一次
*/
@Scheduled(cron = "0 */5 * * * ?")
public void setMachineRoomState(){
log.info("执行了");
}
/**
* 执行完成后五分钟执行一次
*/
@Scheduled(fixedDelay = 1000 * 60 * 5)
public void setMachineRoomState(){
log.info("执行了");
}
/**
* 间隔五分钟执行一次
*/
@Scheduled(fixedRate = 1000 * 60 * 5)
public void setMachineRoomState(){
log.info("执行了");
}
}
@Scheduled注解
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
// cron表达式
String cron() default "";
//corn表达式的时区,默认为空,使用服务器的local time zone。
String zone() default "";
// 周期为在最后一次调用结束和下一次调用开始,以毫秒为单位,固定周期地执行注释的方法。
long fixedDelay() default -1;
//周期间隔为在最后一次调用结束和下一次调用开始,以毫秒为单位,固定周期地执行注释的方法。
String fixedDelayString() default "";
//固定周期地执行注解方法,周期为调用间隔,单位为毫秒
long fixedRate() default -1;
String fixedRateString() default "";
// fixedRate() 或fixedDelay()第一次执行前延迟的毫秒数
long initialDelay() default -1;
// fixedRate() 或fixedDelay()第一次执行前延迟的毫秒数
String initialDelayString() default "";
}
cron表达式
cron的表达式是字符串,实际上是由七子表达式,描述个别细节的时间表。
字段名 | 允许的值 | 允许的特殊字符 |
---|---|---|
秒 | 0-59 | , - * / |
分 | 0-59 | , - * / |
小时 | 0-23 | , - * / |
日 | 1-31 | , - * ? / L W C |
月 | 1-12 or JAN-DEC | , - * / |
周几 | 0-6 or SUN-SAT | , - * ? / L C # |
年 (可选字段) | empty, 1970-2099 | , - * / |
“*” 代表整个时间段
“?”字符:表示不确定的值
“,”字符:指定数个值
“-”字符:指定一个值的范围
“/”字符:指定一个值的增加幅度。n/m表示从n开始,每次增加m
“L”字符:用在日表示一个月中的最后一天,用在周表示该月最后一个星期X
“W”字符:指定离给定日期最近的工作日(周一到周五)
“#”字符:表示该月第几个周X。6#3表示该月第3个周五
eg:
每隔5秒执行一次:*/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月1号凌晨1点执行一次:0 0 1 1 * ?
每月最后一天23点执行一次:0 0 23 L * ?
每周星期天凌晨1点执行一次:0 0 1 ? * L 或 0 0 1 ? * 0
每周一下午1点执行一次:0 0 13 ? * 1
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?
Quartz
中cron表达式可以由最多7个字段构成,即:秒、分、时、日、月、周、年,最后一个字段”年“则可以为空;
对于周几,即 “Day-of-Week” 其值 1,2,3,4,5,6,7分别表示 “SUN,MON,TUE,WED,THU,FRI,SAT”;
SpringBoot schedule cron
表达式分析:将cronExpression字符串中的 "SUN,MON,TUE,WED,THU,FRI,SAT"分别替换成了”0,1,2,3,4,5,6“;如果把握不住,周几就不要用数字表示了,直接用英文缩写:SUN
,MON
,TUE
,WED
,THU
,FRI
,SAT
。
Corn表达式在线验证:http://cron.qqe2.com/
因为定时任务默认是单线程执行,多线程执行设置
@Bean
public ScheduledThreadPoolExecutor scheduledExecutorService() {
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(10);
return executor;
}
- 解决每月最后一天无法使用L问题
@Scheduled(cron = "0 30 23 28-31 * ?") //每个月31号执行
public void reptilian() {
final Calendar calendar = Calendar.getInstance();
//如果不是最后一天
if (!(calendar.get(Calendar.DATE) == calendar.getActualMaximum(Calendar.DATE))) {
return;
}
这里插入业务逻辑判断
}
在springboot项目中结合quartz实现定时调度时,cron表达式无法使用L关键字定时到每月最后一天,这时可以使用创建日历类去实现
springboot配置Quartz开始多线程
Spring Boot提供的@Scheduled注解默认是以单线程方式执行
Scheduled
提供的核心类ScheduledTaskRegistrar
、ContextLifecycleScheduledTaskRegistrar
ContextLifecycleScheduledTaskRegistrar
在bean实例化后会调 其父类ScheduledTaskRegistrar提供的scheduleTasks()
方法,并且创建了单例的调度线程池
由此可见,Spring Boot提供的@Scheduled注解默认是以单线程
方式执行。
在单线程下,如果某个任务处理时间很长,有可能导致其他任务阻塞。testTask1()休眠一小时模拟长时间处理任务,testTask2()一直处于阻塞状态。
配置多线程
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.ScheduledThreadPoolExecutor;
/**
* 配置 Schedule 多线程
*/
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(new ScheduledThreadPoolExecutor(10));
}
}
SpringBoot动态配置开启关闭定时任务
- 背景介绍:
springboot定时任务是通过@EnableScheduling
注解的方式进行启用的,但是实际开发中本地是不需要进行开启的,而部署至生产环境需要进行开启,那么每次手动进行设置比较繁琐,下面通过配置文件的方式进行配置服务的启用和关闭配置方式;
1:配置文件添加配置项;
enable:
scheduling: false
2:添加SchedulerCondition;
public class SchedulerCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return Boolean.valueOf(context.getEnvironment().getProperty("enable.scheduling"));
}
}
3:添加Scheduler类;
@Configuration
public class Scheduler {
@Conditional(SchedulerCondition.class)
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}
完成以上两个文件的配置后,
在项目的启动类中可以去掉@EnableScheduling注解
,然后在配置文件中加入enable.scheduling的配置项即可,以yml文件为例,false关闭,true开启,这样在不同的配置文件中进行设置即可实现不同环境下的定时任务的配置了
参考链接:
https://blog.csdn.net/zty1317313805/article/details/129200052
https://www.cnblogs.com/summerday152/p/14193968.html
https://www.cnblogs.com/hongwz/p/5831036.html