Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。
Quartz 可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用。
Quartz 允许程序开发人员根据时间的间隔来调度作业。
Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。
拿火车票购票来说,当你下单后,后台就会插入一条待支付的task(job),一般是30分钟,超过30min后就会执行这个job,去判断你是否支付,未支付就会取消此次订单;当你支付完成之后,后台拿到支付回调后就会再插入一条待消费的task(job),Job触发日期为火车票上的出发日期,超过这个时间就会执行这个job,判断是否使用等。
在实际项目中,当Job过多,肯定不能人工操作,这时候就需要任务调度框架来帮我们自动执行这些程序,那该如何去实现?
org.quartz-scheduler
quartz
2.3.2
package cn.com.mochasoft.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.time.LocalDate;
import java.util.Random;
/**
* 定时任务入门案例
* 创建Job,定时输出当前时间
* @author: zhouxb
*
* */
public class PrintWordsJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println(jobExecutionContext.getJobDetail().getJobDataMap().get("jobDetail"));
System.out.println(jobExecutionContext.getTrigger().getJobDataMap().get("trigger1"));
String printTime = LocalDate.now().toString();
System.out.println("PrintWordsJob start at : " + printTime + ", prints: Hello Job-" + new Random().nextInt(100));
}
}
package cn.com.mochasoft.schedule;
import cn.com.mochasoft.job.PrintWordsJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
/**
* 定时任务调度器
*
* @author zhouxiaobing
*/
public class MyScheduler {
public static void main(String[] args) throws SchedulerException, InterruptedException {
//创建调度器Scheduler
StdSchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
//创建JobDetail实例,并与printWordsJob类绑定()
JobDetail jobDetail = JobBuilder.newJob(PrintWordsJob.class)
.withIdentity("jobDetail", "group")
.usingJobData("jobDetail", "这是MyScheduler的jobDetail")
.build();
//构建Trigger实例,每隔1s执行一次
SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger", "triggerGroup")
.usingJobData("trigger", "这是jobDetail的trigger")
.startNow() //立即生效
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(30) //每隔30s执行一次
.repeatForever()) //一直执行
.build();
//执行
scheduler.scheduleJob(jobDetail, trigger);
System.out.println("-----------------------scheduler start ! ------------------------");
scheduler.start();
//睡眠
TimeUnit.MINUTES.sleep(1);
scheduler.shutdown();
System.out.println("-----------------------scheduler shutdown ! ------------------------");
}
}
Job是Quartz的一个接口,接口中只有一个execute方法,用于编辑具体任务逻辑
JobDetail绑定Job,并为Job提供属性:
由于任务有可能存在并发执行的情况,如果Scheduler直接调用Job, 就会存在对同一个Job实例并发访问的问题, 使用JobDetail & Job的方式, Scheduler每次执行, 都会根据JobDetail创建一个新的Job实例,规避了并发访问的问题.
JobExecutionContext中包含了Quartz运行时的环境以及Job本身的详细数据信息.
当Scheduler调度执行一个Job, 就会将JobExecutionContext传递给该Job的execute()中, Job可以通过JobExecutionContext对象获取信息, 主要信息有:
JobDataMap实现了JDK的Map接口, 可以以K-V形式存储数据. JobDetail, Trigger都可以使用JobDataMap来设置一些参数或信息
在Job执行execute()方法的时候, JobExecutionContext可以获取这些信息
控制台输出结果:
Trigger是Quartz触发器, 会去通知Scheduler何时执行对应Job, 采用的是builder模式,常用方法:
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1)
.repeatForever()).build();
查看SchedulerBuilder, 这个是抽象类,其共有4中具体实现方法, 如图:
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInHours(1)
.repeatForever()).build();
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1")
//加入 scheduler之后立刻执行
.startNow()
//定时 ,每个1秒钟执行一次
.withSchedule(dailyTimeIntervalSchedule()
.startingDailyAt(TimeOfDay.hourAndMinuteOfDay(9, 0)) //第天9:00开始
.endingDailyAt(TimeOfDay.hourAndMinuteOfDay(16, 0)) //16:00 结束
.onDaysOfTheWeek(MONDAY,TUESDAY,WEDNESDAY,THURSDAY) //周一至周五执行
.withIntervalInHours(1) //每间隔1小时执行一次
).build();
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1")
//加入 scheduler之后立刻执行
.startNow()
//定时 ,每个1秒钟执行一次
.withSchedule(calendarIntervalSchedule()
.withIntervalInWeeks(1) //每周执行一次
).build();
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1")
//加入 scheduler之后立刻执行
.startNow()
//定时 ,每个1秒钟执行一次
.withSchedule(cronSchedule("0 0/2 8-17 * * ?") // 每天8:00-17:00,每隔2分钟执行一次
).build();
推荐网站:在线生成Cron
Scheduler是Quartz核心所在,所有任务都是通过Scheduler调度开始.
Scheduler是一个接口类, 所有具体实现类都是通过SchedulerFactory工厂类实现, 但是Schedulerfactory
有两个具体实现类, 如图:
项目中出现了一种情况,本来job执行时间只需要10s,但是由于数据库量增大之后,执行时间变成了60s,而我设置的间隔时间是30s,这样就会出现上次任务还没执行完成,下次任务就开始执行了。所以,在这种情况下,我们要禁止quart的并发操作.
3. Spring中将job的concurrent属性设为false, 默认为true
spring官网有具体的配置文档,实现quartz与springboot整合
查看spring boot features
Spring Boot为使用Quartz调度器提供了多种方便,包括"Spring-Boot-Starter-Quartz"依赖。如果Quartz可用,则调度器通过SchedulerFactoryBean自动配置。
org.springframework.boot
spring-boot-starter-quartz
按照文档提供的案例,Job需要继承QuartzJobBean
package com.mochasoft.job;
import com.mochasoft.service.HelloSpringService;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.time.LocalTime;
import java.util.StringJoiner;
public class SpringBootJob1 extends QuartzJobBean { // 继承QuartzJobBean
// 实现业务层自动注入
@Autowired
private HelloSpringService helloSpringService;
// 执行代码块
@Override
protected void executeInternal(final JobExecutionContext jobExecutionContext) throws JobExecutionException {
final StringJoiner outStr = new StringJoiner("|") // 分隔符
.add("SpringBootJob1.executeInternal")
.add("nowDateTime: " + LocalTime.now())
.add(helloSpringService.HelloSpring());
System.out.println(outStr);
}
}
HelloService代码块
package com.mochasoft.service;
import org.springframework.stereotype.Service;
@Service
public class HelloSpringService {
// @Autowired
// private DataSource dataSource;
public String HelloSpring() {
return "hello Spring";
}
//
// @PostConstruct
// public void test() {
// System.out.println(dataSource.toString());
// }
}
package com.mochasoft.jobconfig;
import com.mochasoft.job.SpringBootJob1;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class JobInit {
// springboot自动注入Scheduler调度器
@Autowired
private Scheduler scheduler;
/**
* @PostConstruct
* 修饰非静态void()方法。该方法会在服务器家在Servlet时候运行,并且只会被服务器执行一次。
* 该方法在构造函数之后执行,init()方法之前执行。
* 该注解的方法在整个Bean初始化中的执行顺序:
* Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
* */
@PostConstruct
public void initJob() throws SchedulerException {
// 查看当前线程数
//System.out.println("quartz线程数为:" + scheduler.getMetaData().getThreadPoolSize());
// 创建JobDetail
JobDetail jobDetail = JobBuilder.newJob(SpringBootJob1.class)
.build();
// 创建Trigger
Trigger trigger = TriggerBuilder.newTrigger()
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(10)) // 每隔10s执行一次,一直执行下去
.startNow() // 立即开始
.build();
// 调度器执行
scheduler.scheduleJob(jobDetail, trigger);
}
}
package com.mochasoft.jobconfig;
import com.mochasoft.job.SpringBootJob1;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration //声明该类为配置类,交给Spring管理
public class JobConfig {
// 自动注入Scheduler
@Autowired
private Scheduler scheduler;
// @Bean
// @QuartzDataSource
// public DataSource dataSource() {
// DriverManagerDataSource dataSource = new DriverManagerDataSource();
// dataSource.setUsername("zhouxb");
// dataSource.setPassword("zzzz");
// return dataSource;
// }
// 创建JobDetail,并交给Spring管理
@Bean
public JobDetail jobDetail(){
return JobBuilder.newJob(SpringBootJob1.class)
.withIdentity("jobDetail") //标识身份
.storeDurably() // 持久化jobDetail
.build();
}
// 创建Trigger,并交给Spring管理
@Bean
public Trigger trigger() {
return TriggerBuilder.newTrigger()
.forJob("jobDetail") // 执行指定JobDetail
.startNow()
.build();
}
}
实现Springboot自动注入Scheduler方法有两个:
最近在做项目,项目中有个需求:需要使用定时任务,这个定时任务需要即时生效。
查看Quartz官网之后发现:Quartz提供两种基本作业存储类型:
如果需要做持久化的话,数据肯定是要存在数据库的,那么到底存在哪些表呢?
## 说明 下面的所有表都要创建 要不然会出现错误 在mysql的命令行执行即可
## 保存job详细信息的表
1.qrtz_blob_triggers : 以Blob 类型存储的触发器。
2.qrtz_calendars:存放日历信息, quartz可配置一个日历来指定一个时间范围。
3.qrtz_cron_triggers:存放cron类型的触发器。
4.qrtz_fired_triggers:存放已触发的触发器。
5.qrtz_job_details:存放一个jobDetail信息。
6.qrtz_job_listeners:job**监听器**。
7.qrtz_locks: 存储程序的悲观锁的信息(假如使用了悲观锁)。
8.qrtz_paused_trigger_graps:存放暂停掉的触发器。
9.qrtz_scheduler_state:调度器状态。
10.qrtz_simple_triggers:简单触发器的信息。
11.qrtz_triggers:触发器的基本信息。
————————————————
CREATE TABLE QRTZ_JOB_DETAILS(
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,##job的名字
JOB_GROUP VARCHAR(200) NOT NULL,##job的所属组的名字
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,##job实现类的完全包名,quartz就是根据这个路径到classpath找到该job类
IS_DURABLE VARCHAR(1) NOT NULL,#是否持久化,把该属性设置为1,quartz会把job持久化到数据库中
IS_NONCONCURRENT VARCHAR(1) NOT NULL,
IS_UPDATE_DATA VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,#一个blob字段,存放持久化job对象
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;
#保存trigger信息 触发器信息表
CREATE TABLE QRTZ_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,#trigger的名字,
TRIGGER_GROUP VARCHAR(200) NOT NULL,#trigger所属组的名字
JOB_NAME VARCHAR(200) NOT NULL,#qrtz_job_details表job_name的外键
JOB_GROUP VARCHAR(200) NOT NULL,#qrtz_job_details表job_group的外键
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状态,设置为ACQUIRED,如果设置为WAITING,则job不会触发
TRIGGER_TYPE VARCHAR(8) NOT NULL,#CRON
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))
ENGINE=InnoDB;
#存储cron表达式表
CREATE TABLE QRTZ_CRON_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,#triggers表trigger_name的外键
TRIGGER_GROUP VARCHAR(200) NOT NULL,# qrtz_triggers表trigger_group的外键
CRON_EXPRESSION VARCHAR(120) NOT NULL,#cron表达式
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))
ENGINE=InnoDB;
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))
ENGINE=InnoDB;
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))
ENGINE=InnoDB;
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),
INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;
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))
ENGINE=InnoDB;
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;
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))
ENGINE=InnoDB;
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))
ENGINE=InnoDB;
CREATE TABLE QRTZ_LOCKS (
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME))
ENGINE=InnoDB;
COMMIT;
具体的job 触发器 表的数据
# 插入要执行的 job
INSERT INTO `QRTZ_JOB_DETAILS` VALUES ('scheduler', 'registerDoctorDetail', 'DEFAULT', NULL, 'com.xxx.xxx.business.quartz.jobs.RegisterRongDoctor', '1', '0', '0', '0', null);
#Trigger
INSERT INTO `QRTZ_TRIGGERS` VALUES ('scheduler', 'register_doctor', 'register_group', 'registerDoctorDetail', 'DEFAULT', NULL, 0, 0, 0, 'ACQUIRED', 'CRON', 1502208000, 0, NULL, 0, null);
#trigger express
INSERT INTO `QRTZ_CRON_TRIGGERS` VALUES ('scheduler', 'register_doctor', 'register_group', '0/30 * * * * ?', 'GMT+08');
COMMIT;
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-data-jdbc
spring:
datasource:
url: jdbc:mysql://127.0.0.1/quartzTest?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
quartz:
job-store-type: JDBC
jdbc:
initialize-schema: always
package com.mochasoft.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.sql.DataSource;
import javax.annotation.PostConstruct;
@Service
public class HelloSpringService {
//自动注入数据源
@Autowired
private DataSource dataSource;
public String HelloSpring() {
return "hello Spring";
}
// 查看数据源是否注入并打印
@PostConstruct
public void test() {
System.out.println(dataSource.toString());
}
}
注释掉JobConfig上的@Configuration注解
package com.mochasoft.jobconfig;
import com.mochasoft.job.SpringBootJob1;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class JobInit {
// springboot自动注入Scheduler调度器
@Autowired
private Scheduler scheduler;
/**
* @PostConstruct
* 修饰非静态void()方法。该方法会在服务器家在Servlet时候运行,并且只会被服务器执行一次。
* 该方法在构造函数之后执行,init()方法之前执行。
* 该注解的方法在整个Bean初始化中的执行顺序:
* Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
* */
@PostConstruct
public void initJob() throws SchedulerException {
// 查看当前线程数
System.out.println("quartz线程数为:" + scheduler.getMetaData().getThreadPoolSize());
// 创建JobDetail
JobDetail jobDetail = JobBuilder.newJob(SpringBootJob1.class)
.build();
// 创建Trigger
Trigger trigger = TriggerBuilder.newTrigger()
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(10)) // 每隔10s执行一次,一直执行下去
.startNow() // 立即开始
.build();
// 调度器执行
scheduler.scheduleJob(jobDetail, trigger);
}
}