SpringBoot 整合Quartz定时任务管理【SpringBoot系列18】
SpringCloud 大型系列课程正在制作中,欢迎大家关注与提意见。
程序员每天的CV 与 板砖,也要知其所以然,本系列课程可以帮助初学者学习 SpringBooot 项目开发 与 SpringCloud 微服务系列项目开发
Quartz是由Java语言编写,是OpenSymphony开源组织在Job scheduling领域的项目。
1 项目信息
首先是在项目的 pom.xml 文件中添加依赖如下
<!-- 定时任务--><!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-quartz --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId><version>2.7.9</version></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId></dependency>
然后添加了下定时任务的配置信息
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;import javax.sql.DataSource;
import java.util.Properties;/* 定时任务配置 @author 早起的年轻人* @since 2.0.0 2023-04-20*/
@Configuration
public class ScheduleConfig {@Beanpublic SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {SchedulerFactoryBean factory = new SchedulerFactoryBean();factory.setDataSource(dataSource);//quartz参数Properties prop = new Properties();//#调度器实例名称prop.put("org.quartz.scheduler.instanceName", "RenrenScheduler");//#调度器实例编号自动生成prop.put("org.quartz.scheduler.instanceId", "AUTO");//线程池配置prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");prop.put("org.quartz.threadPool.threadCount", "20");prop.put("org.quartz.threadPool.threadPriority", "5");//JobStore配置prop.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore");//集群配置 开启分布式部署prop.put("org.quartz.jobStore.isClustered", "true");prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");prop.put("org.quartz.jobStore.misfireThreshold", "12000");prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");//PostgreSQL数据库,需要打开此注释//prop.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate");
// prop.put(" org.quartz.jobStore.driverDelegateClass "," org.quartz.impl.jdbcjobstore.StdJDBCDelegate");factory.setQuartzProperties(prop);factory.setSchedulerName("RenrenScheduler");//延时启动factory.setStartupDelay(10);factory.setApplicationContextSchedulerContextKey("applicationContextKey");//可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了factory.setOverwriteExistingJobs(true);//设置自动启动,默认为truefactory.setAutoStartup(true);return factory;}
}
然后添加 Quartz 所需要的表结构
/*Navicat Premium Data TransferSource Server : mac-docker-1Source Server Type : MySQLSource Server Version : 50725Source Host : 127.0.0.1:3306Source Schema : spring_bootTarget Server Type : MySQLTarget Server Version : 50725File Encoding : 65001Date: 19/03/2023 15:00:20
*/SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for QRTZ_BLOB_TRIGGERS
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_BLOB_TRIGGERS`;
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,PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),CONSTRAINT `QRTZ_BLOB_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Table structure for QRTZ_CALENDARS
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_CALENDARS`;
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 DEFAULT CHARSET=utf8;-- ----------------------------
-- Table structure for QRTZ_CRON_TRIGGERS
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_CRON_TRIGGERS`;
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) DEFAULT NULL,PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),CONSTRAINT `QRTZ_CRON_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Table structure for QRTZ_FIRED_TRIGGERS
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_FIRED_TRIGGERS`;
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` int(11) NOT NULL,`STATE` varchar(16) NOT NULL,`JOB_NAME` varchar(200) DEFAULT NULL,`JOB_GROUP` varchar(200) DEFAULT NULL,`IS_NONCONCURRENT` varchar(1) DEFAULT NULL,`REQUESTS_RECOVERY` varchar(1) DEFAULT NULL,PRIMARY KEY (`SCHED_NAME`,`ENTRY_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Table structure for QRTZ_JOB_DETAILS
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_JOB_DETAILS`;
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) DEFAULT 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,PRIMARY KEY (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Table structure for QRTZ_LOCKS
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_LOCKS`;
CREATE TABLE `QRTZ_LOCKS` (`SCHED_NAME` varchar(120) NOT NULL,`LOCK_NAME` varchar(40) NOT NULL,PRIMARY KEY (`SCHED_NAME`,`LOCK_NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Table structure for QRTZ_PAUSED_TRIGGER_GRPS
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_PAUSED_TRIGGER_GRPS`;
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 DEFAULT CHARSET=utf8;-- ----------------------------
-- Table structure for QRTZ_SCHEDULER_STATE
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_SCHEDULER_STATE`;
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 DEFAULT CHARSET=utf8;-- ----------------------------
-- Table structure for QRTZ_SIMPLE_TRIGGERS
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_SIMPLE_TRIGGERS`;
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`),CONSTRAINT `QRTZ_SIMPLE_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Table structure for QRTZ_SIMPROP_TRIGGERS
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_SIMPROP_TRIGGERS`;
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) DEFAULT NULL,`STR_PROP_2` varchar(512) DEFAULT NULL,`STR_PROP_3` varchar(512) DEFAULT NULL,`INT_PROP_1` int(11) DEFAULT NULL,`INT_PROP_2` int(11) DEFAULT NULL,`LONG_PROP_1` bigint(20) DEFAULT NULL,`LONG_PROP_2` bigint(20) DEFAULT NULL,`DEC_PROP_1` decimal(13,4) DEFAULT NULL,`DEC_PROP_2` decimal(13,4) DEFAULT NULL,`BOOL_PROP_1` varchar(1) DEFAULT NULL,`BOOL_PROP_2` varchar(1) DEFAULT NULL,PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),CONSTRAINT `QRTZ_SIMPROP_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Table structure for QRTZ_TRIGGERS
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_TRIGGERS`;
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) DEFAULT NULL,`NEXT_FIRE_TIME` bigint(13) DEFAULT NULL,`PREV_FIRE_TIME` bigint(13) DEFAULT NULL,`PRIORITY` int(11) DEFAULT NULL,`TRIGGER_STATE` varchar(16) NOT NULL,`TRIGGER_TYPE` varchar(8) NOT NULL,`START_TIME` bigint(13) NOT NULL,`END_TIME` bigint(13) DEFAULT NULL,`CALENDAR_NAME` varchar(200) DEFAULT NULL,`MISFIRE_INSTR` smallint(2) DEFAULT NULL,`JOB_DATA` blob,PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),KEY `SCHED_NAME` (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`),CONSTRAINT `QRTZ_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`) REFERENCES `QRTZ_JOB_DETAILS` (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;SET FOREIGN_KEY_CHECKS = 1;
本文章接 SpringBoot ElasticSearch 实现订单的分页查询 【SpringBoot系列17】
2 服务启动时 加载定时任务
ScheduleJobService.java
public interface ScheduleJobService extends IService<ScheduleJobEntity> {/ 保存定时任务*/boolean newObj(ScheduleJobEntity scheduleJob);/更新定时任务*/void update(ScheduleJobEntity scheduleJob);
}
ScheduleJobServiceImpl.java 实现类
@Service("scheduleJobService")
public class ScheduleJobServiceImpl extends ServiceImpl<ScheduleJobDao, ScheduleJobEntity> implements ScheduleJobService {@Resourceprivate Scheduler scheduler;/* 项目启动时,初始化定时器*/@PostConstructpublic void init() {//查询当前所有的定时任务List<ScheduleJobEntity> scheduleJobList = this.list(new QueryWrapper<>());try {scheduler.clear();} catch (SchedulerException e) {e.printStackTrace();}for (ScheduleJobEntity scheduleJob : scheduleJobList) {//获取任务的表达式触发器CronTrigger cronTrigger = ScheduleUtils.getCronTrigger(scheduler, scheduleJob.getJobId());//如果不存在,则创建if (cronTrigger == null) {ScheduleUtils.createScheduleJob(scheduler, scheduleJob);} else {ScheduleUtils.updateScheduleJob(scheduler, scheduleJob);}}}
}
新建定时任务与更新定时任务如下:
@Override@Transactional(rollbackFor = Exception.class)public boolean newObj(ScheduleJobEntity scheduleJob) {scheduleJob.setCreateTime(new Date());//设置状态 默认是启动scheduleJob.setStatus(Constant.ScheduleStatus.NORMAL.getValue());//cron表达式String cronExpression = scheduleJob.getCronExpression();//验证boolean valid = CronExpression.isValidExpression(cronExpression);if(!valid){throw new RuntimeException("cron 表达式不正确");}//保存任务this.save(scheduleJob);//构建job信息ScheduleUtils.createScheduleJob(scheduler, scheduleJob);return false;}@Override@Transactional(rollbackFor = Exception.class)public void update(ScheduleJobEntity scheduleJob) {ScheduleUtils.updateScheduleJob(scheduler, scheduleJob);//更新数据库this.updateById(scheduleJob);}
ScheduleJobEntity.java 类
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;
import java.util.Date;/* 定时任务*/
@TableName("schedule_job")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ScheduleJobEntity implements Serializable {private static final long serialVersionUID = 1L;/* 任务调度参数key*/public static final String JOB_PARAM_KEY = "JOB_PARAM_KEY";/* 任务id*/@TableIdprivate Long jobId;/* spring bean名称*/private String beanName;/* 方法名*/private String methodName;/* 参数*/private String params;/* cron表达式*/private String cronExpression;/* 任务状态*/private Integer status;/* 备注*/private String remark;/* 创建时间*/private Date createTime;}
3 定时任务核心调度 ScheduleUtils
/* 定时任务工具类 @author 早起的年轻人* @since 1.2.0 2022-11-28*/
public class ScheduleUtils {private final static String JOB_NAME = "TASK_";/* 获取触发器key*/public static TriggerKey getTriggerKey(Long jobId) {return TriggerKey.triggerKey(JOB_NAME + jobId);}/* 获取jobKey*/public static JobKey getJobKey(Long jobId) {return JobKey.jobKey(JOB_NAME + jobId);}/* 获取表达式触发器*/public static CronTrigger getCronTrigger(Scheduler scheduler, Long jobId) {try {return (CronTrigger) scheduler.getTrigger(getTriggerKey(jobId));} catch (SchedulerException e) {throw new RuntimeException("获取定时任务CronTrigger出现异常", e);}}/* 创建定时任务*/public static void createScheduleJob(Scheduler scheduler, ScheduleJobEntity scheduleJob) {try {JobKey jobKey = getJobKey(scheduleJob.getJobId());//构建job信息JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class).withIdentity(jobKey).build();//表达式调度构建器CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression()).withMisfireHandlingInstructionDoNothing();//按新的cronExpression表达式构建一个新的triggerCronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(scheduleJob.getJobId())).withSchedule(scheduleBuilder).build();Gson gson = new Gson();//放入参数,运行时的方法可以获取jobDetail.getJobDataMap().put(ScheduleJobEntity.JOB_PARAM_KEY, gson.toJson(scheduleJob));scheduler.scheduleJob(jobDetail, trigger);//暂停任务if(scheduleJob.getStatus() == Constant.ScheduleStatus.PAUSE.getValue()){pauseJob(scheduler, scheduleJob.getJobId());}} catch (SchedulerException e) {throw new RuntimeException("创建定时任务失败 ", e);}}/* 更新定时任务*/public static void updateScheduleJob(Scheduler scheduler, ScheduleJobEntity scheduleJob) {try {TriggerKey triggerKey = getTriggerKey(scheduleJob.getJobId());//表达式调度构建器CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression()).withMisfireHandlingInstructionDoNothing();CronTrigger trigger = getCronTrigger(scheduler, scheduleJob.getJobId());//按新的cronExpression表达式重新构建triggertrigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();Gson gson = new Gson();//参数trigger.getJobDataMap().put(ScheduleJobEntity.JOB_PARAM_KEY, gson.toJson(scheduleJob));scheduler.rescheduleJob(triggerKey, trigger);//暂停任务if(scheduleJob.getStatus() == Constant.ScheduleStatus.PAUSE.getValue()){pauseJob(scheduler, scheduleJob.getJobId());}} catch (SchedulerException e) {throw new RuntimeException("更新定时任务失败", e);}}/* 立即执行任务*/public static void run(Scheduler scheduler, ScheduleJobEntity scheduleJob) {try {//参数JobDataMap dataMap = new JobDataMap();Gson gson = new Gson();dataMap.put(ScheduleJobEntity.JOB_PARAM_KEY,gson.toJson(scheduleJob));scheduler.triggerJob(getJobKey(scheduleJob.getJobId()), dataMap);} catch (SchedulerException e) {throw new RuntimeException("立即执行定时任务失败 "+e.getMessage(), e);}}/* 暂停任务*/public static void pauseJob(Scheduler scheduler, Long jobId) {try {scheduler.pauseJob(getJobKey(jobId));} catch (SchedulerException e) {throw new RuntimeException("暂停定时任务失败", e);}}/* 恢复任务*/public static void resumeJob(Scheduler scheduler, Long jobId) {try {scheduler.resumeJob(getJobKey(jobId));} catch (SchedulerException e) {throw new RuntimeException("暂停定时任务失败", e);}}/* 删除定时任务*/public static void deleteScheduleJob(Scheduler scheduler, Long jobId) {try {scheduler.deleteJob(getJobKey(jobId));} catch (SchedulerException e) {throw new RuntimeException("删除定时任务失败", e);}}
}
项目源码在这里 :https://gitee.com/android.long/spring-boot-study/tree/master/biglead-api-13-quartz
有兴趣可以关注一下公众号:biglead
- 创建SpringBoot基础项目
- SpringBoot项目集成mybatis
- SpringBoot 集成 Druid 数据源【SpringBoot系列3】
- SpringBoot MyBatis 实现分页查询数据【SpringBoot系列4】
- SpringBoot MyBatis-Plus 集成 【SpringBoot系列5】
- SpringBoot mybatis-plus-generator 代码生成器 【SpringBoot系列6】
- SpringBoot MyBatis-Plus 分页查询 【SpringBoot系列7】
- SpringBoot 集成Redis缓存 以及实现基本的数据缓存【SpringBoot系列8】
- SpringBoot 整合 Spring Security 实现安全认证【SpringBoot系列9】
- SpringBoot Security认证 Redis缓存用户信息【SpringBoot系列10】
- SpringBoot 整合 RabbitMQ 消息队列【SpringBoot系列11】
- SpringBoot 结合RabbitMQ与Redis实现商品的并发下单【SpringBoot系列12】
- SpringBoot 雪花算法生成商品订单号【SpringBoot系列13】
- SpringBoot RabbitMQ 延时队列取消订单【SpringBoot系列14】
- SpringBoot RabbitMQ 商品秒杀【SpringBoot系列15】
- SpringBoot ElasticSearch 【SpringBoot系列16】
- SpringBoot ElasticSearch 实现订单的分页查询 【SpringBoot系列17】