Quartz的快速入门见:
Quartz官方文档_w3cschool
Quartz 存储方式有两种:MEMORY 和 JDBC。默认是内存形式维护任务信息,服务重启了任务就重新开始执行;而JDBC形式就是能够把任务信息持久化到数据库,虽然服务重启了,但根据数据库中的记录,任务还能继续执行。
org.springframework.boot
spring-boot-starter-quartz
新建工程,pom增加quartz依赖
新建一个Job类,继承QuartzJobBean,实现抽象类中的方法,方法中实现该定时任务需要执行的逻辑。
比如我新建一个PrintTipJob,每次执行时在控制台输出hello world!
package com.example.demo.job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
public class PrintTipJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
LocalTime time = LocalTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
System.out.println("time:" + time.format(formatter) + " hello world!");
}
}
封装一个用于增加,更新,删除job的service层
service的接口
package com.example.demo.service;
import org.springframework.scheduling.quartz.QuartzJobBean;
public interface QuartzJobService {
/**
* 添加一个任务
* @param job
* @param jobName
* @param jobGroupName
* @param jobCronTime
*/
void addJob(Class<? extends QuartzJobBean> job, String jobName, String jobGroupName, String jobCronTime);
/**
* 更新任务的执行cron
* @param job
* @param jobGroupName
* @param jobCronTime
*/
void updateJob(String job, String jobGroupName, String jobCronTime);
/**
* 是否存在某个job
* @param job
* @param jobGroupName
* @return
*/
boolean existJob(String job, String jobGroupName);
/**
* 删除任务一个job
*
* @param jobName 任务名称
* @param jobGroupName 任务组名
*/
void deleteJob(String jobName, String jobGroupName);
}
service实现类
package com.example.demo.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.example.demo.service.QuartzJobService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Service;
import java.util.Set;
@Slf4j
@Service
public class QuartzJobServiceImpl implements QuartzJobService {
@Autowired
private Scheduler scheduler;
@Override
public void addJob(Class<? extends QuartzJobBean> job, String jobName, String jobGroupName, String jobCronTime) {
JobDetail jobDetail = JobBuilder.newJob(job)
.withIdentity(jobName, jobGroupName)
.withDescription(jobCronTime)
.build();
Trigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName)
.startAt(DateBuilder.futureDate(1, DateBuilder.IntervalUnit.SECOND))
.withSchedule(CronScheduleBuilder.cronSchedule(jobCronTime)).startNow().build();
try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (Exception e) {
log.error("e:{}", e.getMessage());
}
}
@Override
public void updateJob(String job, String jobGroupName, String jobCronTime) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(job, jobGroupName);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (ObjectUtil.isNotNull(trigger)) {
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
.withSchedule(CronScheduleBuilder.cronSchedule(jobCronTime)).build();
} else {
trigger = TriggerBuilder.newTrigger().withIdentity(job, jobGroupName)
.startAt(DateBuilder.futureDate(1, DateBuilder.IntervalUnit.SECOND))
.withSchedule(CronScheduleBuilder.cronSchedule(jobCronTime)).startNow().build();
}
scheduler.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
log.error("e:{}", e.getMessage());
}
}
@Override
public boolean existJob(String job, String jobGroupName) {
try {
if (StringUtils.isAnyBlank(job, jobGroupName)) {
return false;
}
GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
if (CollectionUtils.isNotEmpty(jobKeys)) {
for (JobKey jobKey : jobKeys) {
if (jobKey.getName().equals(job) && jobKey.getGroup().equals(jobGroupName)) {
return true;
}
}
}
} catch (Exception e) {
log.error("e:{}", e.getMessage());
}
return false;
}
@Override
public void deleteJob(String jobName, String jobGroupName) {
try {
scheduler.deleteJob(new JobKey(jobName, jobGroupName));
} catch (Exception e) {
log.error("e:{}", e.getMessage());
}
}
}
增加PrintTipJob类,cron表达式0/5 * * * * ? ,每5s执行一次
给出一个cron表达式在线生成器小工具的链接:Cron在线表达式生成器
package com.example.demo.listener;
import com.example.demo.job.PrintTipJob;
import com.example.demo.service.QuartzJobService;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class ApplicationStartQuartzJobListener implements ApplicationListener<ContextRefreshedEvent> {
@Resource
private QuartzJobService quartzJobService;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (quartzJobService.existJob("PrintTipJob", "test")) {
quartzJobService.updateJob("PrintTipJob", "test", "0/5 * * * * ? ");
} else {
quartzJobService.addJob(PrintTipJob.class, "PrintTipJob", "test", "0/5 * * * * ? ");
}
}
}
图1SpringBoot集成Quartz实现定时任务
Quartz的快速入门见:
Quartz官方文档_w3cschool
Quartz 存储方式有两种:MEMORY 和 JDBC。默认是内存形式维护任务信息,服务重启了任务就重新开始执行;而JDBC形式就是能够把任务信息持久化到数据库,虽然服务重启了,但根据数据库中的记录,任务还能继续执行。
org.springframework.boot
spring-boot-starter-quartz
新建工程,pom增加quartz依赖
新建一个Job类,继承QuartzJobBean,实现抽象类中的方法,方法中实现该定时任务需要执行的逻辑。
比如我新建一个PrintTipJob,每次执行时在控制台输出hello world!
package com.example.demo.job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
public class PrintTipJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
LocalTime time = LocalTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
System.out.println("time:" + time.format(formatter) + " hello world!");
}
}
封装一个用于增加,更新,删除job的service层
service的接口
package com.example.demo.service;
import org.springframework.scheduling.quartz.QuartzJobBean;
public interface QuartzJobService {
/**
* 添加一个任务
* @param job
* @param jobName
* @param jobGroupName
* @param jobCronTime
*/
void addJob(Class<? extends QuartzJobBean> job, String jobName, String jobGroupName, String jobCronTime);
/**
* 更新任务的执行cron
* @param job
* @param jobGroupName
* @param jobCronTime
*/
void updateJob(String job, String jobGroupName, String jobCronTime);
/**
* 是否存在某个job
* @param job
* @param jobGroupName
* @return
*/
boolean existJob(String job, String jobGroupName);
/**
* 删除任务一个job
*
* @param jobName 任务名称
* @param jobGroupName 任务组名
*/
void deleteJob(String jobName, String jobGroupName);
}
service实现类
package com.example.demo.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.example.demo.service.QuartzJobService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Service;
import java.util.Set;
@Slf4j
@Service
public class QuartzJobServiceImpl implements QuartzJobService {
@Autowired
private Scheduler scheduler;
@Override
public void addJob(Class<? extends QuartzJobBean> job, String jobName, String jobGroupName, String jobCronTime) {
JobDetail jobDetail = JobBuilder.newJob(job)
.withIdentity(jobName, jobGroupName)
.withDescription(jobCronTime)
.build();
Trigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName)
.startAt(DateBuilder.futureDate(1, DateBuilder.IntervalUnit.SECOND))
.withSchedule(CronScheduleBuilder.cronSchedule(jobCronTime)).startNow().build();
try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (Exception e) {
log.error("e:{}", e.getMessage());
}
}
@Override
public void updateJob(String job, String jobGroupName, String jobCronTime) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(job, jobGroupName);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (ObjectUtil.isNotNull(trigger)) {
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
.withSchedule(CronScheduleBuilder.cronSchedule(jobCronTime)).build();
} else {
trigger = TriggerBuilder.newTrigger().withIdentity(job, jobGroupName)
.startAt(DateBuilder.futureDate(1, DateBuilder.IntervalUnit.SECOND))
.withSchedule(CronScheduleBuilder.cronSchedule(jobCronTime)).startNow().build();
}
scheduler.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
log.error("e:{}", e.getMessage());
}
}
@Override
public boolean existJob(String job, String jobGroupName) {
try {
if (StringUtils.isAnyBlank(job, jobGroupName)) {
return false;
}
GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
if (CollectionUtils.isNotEmpty(jobKeys)) {
for (JobKey jobKey : jobKeys) {
if (jobKey.getName().equals(job) && jobKey.getGroup().equals(jobGroupName)) {
return true;
}
}
}
} catch (Exception e) {
log.error("e:{}", e.getMessage());
}
return false;
}
@Override
public void deleteJob(String jobName, String jobGroupName) {
try {
scheduler.deleteJob(new JobKey(jobName, jobGroupName));
} catch (Exception e) {
log.error("e:{}", e.getMessage());
}
}
}
增加PrintTipJob类,cron表达式0/5 * * * * ? ,每5s执行一次
给出一个cron表达式在线生成器小工具的链接:Cron在线表达式生成器
package com.example.demo.listener;
import com.example.demo.job.PrintTipJob;
import com.example.demo.service.QuartzJobService;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class ApplicationStartQuartzJobListener implements ApplicationListener<ContextRefreshedEvent> {
@Resource
private QuartzJobService quartzJobService;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (quartzJobService.existJob("PrintTipJob", "test")) {
quartzJobService.updateJob("PrintTipJob", "test", "0/5 * * * * ? ");
} else {
quartzJobService.addJob(PrintTipJob.class, "PrintTipJob", "test", "0/5 * * * * ? ");
}
}
}
com.mchange
c3p0
0.9.5.4
mysql
mysql-connector-java
在application.properties文件中加入相关配置。
# 将 Quartz 持久化方式修改为 jdbc
spring.quartz.job-store-type=jdbc
# 实例名称(默认为quartzScheduler)
spring.quartz.properties.org.quartz.scheduler.instanceName=test_scheduler
# 实例节点 ID 自动生成
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
# 修改存储内容使用的类
spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
# 数据源信息
spring.quartz.properties.org.quartz.jobStore.dataSource=quartz_jobs
spring.quartz.properties.org.quartz.dataSource.quartz_jobs.driver=com.mysql.cj.jdbc.Driver
spring.quartz.properties.org.quartz.dataSource.quartz_jobs.URL=jdbc:mysql://localhost:3306/quartz_jobs?useUnicode=true&characterEncoding=utf8
spring.quartz.properties.org.quartz.dataSource.quartz_jobs.user=root
spring.quartz.properties.org.quartz.dataSource.quartz_jobs.password=
下载 Quartz 发布包,下载完成后,解压缩进入 quartz-2.2.3/docs/dbTables 目录,找到匹配数据库的 SQL 文件。
下载地址:
https://www.quartz-scheduler.org/downloads/files/quartz-2.2.3-distribution.tar.gz
使用的是mysql的脚本:tables_mysql.sql,或者使用如下脚本
#
# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
#
# PLEASE consider using mysql with innodb tables to avoid locking issues
#
# In your Quartz properties file, you'll need to set
# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#
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;
此处执行sql时还遇到了另外一个问题,有些表创建时一直报错:Index column size too large. The maximum column size is 767 bytes.解决方式参考链接:MySQL 建索引时遇到 Specified key was too long; max key length is 767 bytes
可以开启集群配置,让定时任务支持分布式任务,本人未实验过,从其他文章中看到补充在这里。
在 application.properties 文件中,加入 Quartz 集群的配置信息:
# 开启集群,多个 Quartz 实例使用同一组数据库表
spring.quartz.properties.org.quartz.jobStore.isClustered=true
注意 Quartz 使用同一组数据库表作集群时,只需要配置相同的 instanceName 实例名称就可以,例如本次都用 test_scheduler.
spring.quartz.properties.org.quartz.scheduler.instanceName=test_scheduler
参考文章:
玩转 Spring Boot 集成篇(定时任务框架Quartz)