-
2022-02-11 23:25:05
因为一些需求需要一个定时任务,但Java的简单的定时任务对于多节点的情况执行会有消息不共享的问题,所以使用quartzlai实现定时任务,再此记录一下开发过程,以下是一个demo,可以直接运行。
本次开发使用的依赖是原生包,因为网络环境的原因没有使用springboot整合包。
项目结构
代码:
pom.xml
其中配置数据源的时候mysql-connector-java和spring-boot-starter-jdbc需要同时配置,不然会报数据源为空的错<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.quartzs</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.1</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz-jobs</artifactId> <version>2.2.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> <version>2.6.3</version> <scope>compile</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.15</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.3.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
myQuartz.properties
配置quartz的一些参数,其中文件名不建议写quartz.properties,因为和quartz框架中默认的配置文件同名,启动的时候会将默认的配置文件覆盖,导致项目报错,报错信息为:Active Scheduler of name ‘JobScheduler’ already registered in Quartz SchedulerRepository
如果使用spring的数据源database要和spring配置相同,org.quartz.jobStore.dataSource#可以为任意字符串,对于scheduler来说此值没有意义,但是可以区分同一系统中多个不同的实例, #如果使用了集群的功能,就必须对每一个实例使用相同的名称,这样使这些实例“逻辑上”是同一个scheduler。 org.quartz.scheduler.instanceName = JobScheduler #可以是任意字符串,但如果是集群,scheduler实例的值必须唯一,可以使用AUTO自动生成。 org.quartz.scheduler.instanceId = AUTO org.quartz.scheduler.rmi.export = false org.quartz.scheduler.rmi.proxy = false # 默认false,若是在执行Job之前Quartz开启UserTransaction,此属性应该为true。 #Job执行完毕,JobDataMap更新完(如果是StatefulJob)事务就会提交。默认值是false,可以在job类上使用@ExecuteInJTATransaction 注解,以便在各自的job上决定是否开启JTA事务。 org.quartz.scheduler.wrapJobExecutionInUserTransaction = false #一个scheduler节点允许接收的trigger的最大数,默认是1,这个值越大,定时任务执行的越多,但代价是集群节点之间的不均衡。 org.quartz.scheduler.batchTriggerAcquisitionMaxCount=1 #线程池的实例类,(一般使用SimpleThreadPool即可满足几乎所有用户的需求) org.quartz.threadPool.class= org.quartz.simpl.SimpleThreadPool #线程数量,不会动态增加 org.quartz.threadPool.threadCount= 10 #线程优先级 org.quartz.threadPool.threadPriority= 5 #加载任务代码的ClassLoader是否从外部继承 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread= true #是否设置调度器线程为守护线程 org.quartz.scheduler.makeSchedulerThreadDaemon: true #选择JDBC的存储方式 org.quartz.jobStore.class= org.quartz.impl.jdbcjobstore.JobStoreTX #存储相关信息表的前缀 org.quartz.jobStore.tablePrefix = QRTZ_ #JobDataMaps是否都为String类型 #(若是true的话,便可不用让更复杂的对象以序列化的形式保存到BLOB列中。以防序列化可能导致的版本号问题) org.quartz.jobStore.useProperties = false #最大能忍受的触发超时时间,如果超时则认为“失误” org.quartz.jobStore.misfireThreshold = 60000 #是否是应用在集群中,当应用在集群中时必须设置为TRUE,否则会出错。 #如果有多个Quartz实例在用同一套数据库时,必须设置为true。 org.quartz.jobStore.isClustered=true #只用于设置了isClustered为true的时候,设置一个频度(毫秒),用于实例报告给集群中的其他实例。 #这会影响到侦测失败实例的敏捷度。 org.quartz.jobStore.clusterCheckinInterval =15000 #这是JobStore能处理的错过触发的Trigger的最大数量。处理太多(2打)很快就会导致数据库表被锁定够长的时间, #这样会妨碍别的(还未错过触发)trigger执行的性能。 org.quartz.jobStore.maxMisfiresToHandleAtATime=20 #设置这个参数为true会告诉Quartz从数据源获取连接后不要调用它的setAutoCommit(false)方法。 #在少数情况下是有用的,比如有一个驱动本来是关闭的,但是又调用这个关闭的方法。但是大部分情况下驱动都要求调用setAutoCommit(false) org.quartz.jobStore.dontSetAutoCommitFalse=false #这必须是一个从LOCKS表查询一行并对这行记录加锁的SQL。假设没有设置,默认值如下。 #{0}会在运行期间被前面配置的TABLE_PREFIX所代替 org.quartz.jobStore.selectWithLockSQL=SELECT * FROM {0}LOCKS WHERE LOCK_NAME = ? FOR UPDATE #值为true时告知Quartz(当使用JobStoreTX或CMT)调用JDBC连接的setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE) 方法。这有助于某些数据库在高负载和长时间事务时锁的超时。 org.quartz.jobStore.txIsolationLevelSerializable=false quartz.job-store-type=jdbc org.quartz.jobStore.dataSource = com.mysql.cj.jdbc.Driver
application.properties
配置数据库连接的时候要配置时区不然会报错,报错为:The server time zone value ‘�й���ʱ��’ is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the ‘serverTimezone’ configuration property) to use a more specifc time zone value if you want to utilizserver.port=8080 spring.application.name=quartz1 spring.profiles.active=quartz # 连接四大参数 spring.datasource.url=jdbc:mysql://localhost:3306/quartz1?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
QuartzConfig.java
初始化Scheduler,注入scheduler的时候要加上@Primay注解,不然会扫描多个注入报错:Field scheduler in com.quartzs.demo.util.QuartzJobManager required a single bean, but 2 were found:
- schedulerFactoryBean: defined by method ‘schedulerFactoryBean’ in class path resource [com/quartzs/demo/config/QuartzConfig.class]
- Scheduler: defined by method ‘scheduler’ in class path resource [com/quartzs/demo/config/QuartzConfig.class]package com.quartzs.demo.config; import org.quartz.Scheduler; import org.quartz.ee.servlet.QuartzInitializerListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.io.ClassPathResource; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import javax.sql.DataSource; import java.io.IOException; import java.util.Properties; @Configuration @EnableScheduling public class QuartzConfig { @Autowired private DataSource dataSource; @Bean public SchedulerFactoryBean schedulerFactoryBean() throws IOException { SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setQuartzProperties(quartzProperties()); factory.setDataSource(dataSource); return factory; } @Bean public Properties quartzProperties() throws IOException { PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); propertiesFactoryBean.setLocation(new ClassPathResource("/myQuartz.properties")); //在quartz.properties中的属性被读取并注入后再初始化对象 propertiesFactoryBean.afterPropertiesSet(); return propertiesFactoryBean.getObject(); } /** * quartz初始化监听器 */ @Bean public QuartzInitializerListener executorListener() { return new QuartzInitializerListener(); } /** * 通过SchedulerFactoryBean获取Scheduler的实例 */ @Primary @Bean(name = "Scheduler") public Scheduler scheduler() throws IOException { return schedulerFactoryBean().getScheduler(); } }
TaskJobFactory.java
2.x版本一下无法注入job,此类解决quartz无法注入bean的问题package com.quartzs.demo.config; import org.quartz.spi.TriggerFiredBundle; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.scheduling.quartz.AdaptableJobFactory; /** * 解决quartz无法注入bean的问题 * @author author * 2022-02-11 15:53 */ public class TaskJobFactory extends AdaptableJobFactory { @Autowired private AutowireCapableBeanFactory capableBeanFactory; @Override protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { //调用父类方法 Object jobInstance = super.createJobInstance(bundle); //进行注入 capableBeanFactory.autowireBean(jobInstance); return jobInstance; } }
HelloJob.java
定时任务的逻辑代码类,业务逻辑都是写在这个里面,需要实现job类
如果需要传参数可以放在JobDataMap里,这是一个string,object的map集合,可以根据jobExecutionContext.getJobDetail().getJobDataMap().get(key);取到参数值,可以放多个package com.quartzs.demo.service; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public class HelloJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { // 业务逻辑 System.out.println("定时任务开始执行"); Object id = jobExecutionContext.getJobDetail().getJobDataMap().get("id"); System.out.println("定时任务的id为{}"+id); } }
QuartzJobManager.java
主要是封装定时任务的启动修改删除的一些工具,其中定时任务修改可以多加一个Class类型传入要执行定时任务的逻辑类,根据class类型可以执行不同的方法,因为涉及我的业务只有一个方法所以没有加package com.quartzs.demo.util; import com.quartzs.demo.service.HelloJob; import lombok.extern.log4j.Log4j2; import org.quartz.*; import org.quartz.impl.matchers.GroupMatcher; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.*; /** * task任务创建个工具类 * * @author author * 2020-02-11 16:04 */ @Component @Log4j2 public class QuartzJobManager { private static QuartzJobManager jobUtil; @Autowired private Scheduler scheduler; public QuartzJobManager() { log.info("QuartzJobManager init"); jobUtil = this; } public static QuartzJobManager getInstance() { log.info("return JobUtil"); return QuartzJobManager.jobUtil; } /** * 创建任务 * * @param jobName 任务名称 * @param jobGroupName 任务所在组名称 * @param cronExpression cron表达式 * @throws Exception */ public void addJob(String jobName, String jobGroupName, String cronExpression) throws Exception { //启动调度器 scheduler.start(); //构建job信息 HelloJob:执行定时任务的具体代码 JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity(jobName, jobGroupName).build(); //表达式调度构造起,任务执行时间 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression); //根据任务执行时间的cron表达式构建trigger触发器 CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName).withSchedule(scheduleBuilder).build(); scheduler.scheduleJob(jobDetail, trigger); } /** * 创建job,可传参,可以新加一个定时任务实现类的参数,本例写死 * * @param jobName 任务名称 * @param jobGroupName 任务所在组名称 * @param cronExpression cron表达式 * @param argMap map形式参数 * @throws Exception */ public void addJob(String jobName, String jobGroupName, String cronExpression, Map<String, Object> argMap) throws Exception { // 启动调度器 scheduler.start(); //构建job信息 JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity(jobName, jobGroupName).build(); //传入参数 jobDetail.getJobDataMap().putAll(argMap); //表达式调度构建器(即任务执行的时间) CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression); //按新的cronExpression表达式构建一个新的trigger CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName) .withSchedule(scheduleBuilder).build(); //获得JobDataMap,写入数据 //trigger.getJobDataMap().putAll(argMap); scheduler.scheduleJob(jobDetail, trigger); } /** * 暂停job * * @param jobName 任务名称 * @param jobGroupName 任务所在组名称 * @throws SchedulerException */ public void pauseJob(String jobName, String jobGroupName) throws SchedulerException { scheduler.pauseJob(JobKey.jobKey(jobName, jobGroupName)); } /** * 恢复job * * @param jobName 任务名称 * @param jobGroupName 任务所在组名称 * @throws SchedulerException */ public void resumeJob(String jobName, String jobGroupName) throws SchedulerException { scheduler.resumeJob(JobKey.jobKey(jobName, jobGroupName)); } /** * job 更新,只更新频率 * * @param jobName 任务名称 * @param jobGroupName 任务所在组名称 * @param cronExpression cron表达式 * @throws Exception */ public void updateJob(String jobName, String jobGroupName, String cronExpression) throws Exception { TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName); // 表达式调度构建器 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); // 按新的cronExpression表达式重新构建trigger trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); // 按新的trigger重新设置job执行 scheduler.rescheduleJob(triggerKey, trigger); } /** * job 更新,更新频率和参数 * * @param jobName 任务名称 * @param jobGroupName 任务所在组名称 * @param cronExpression cron表达式 * @param argMap 参数 * @throws Exception */ public void updateJob(String jobName, String jobGroupName, String cronExpression, Map<String, Object> argMap) throws Exception { TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName); // 表达式调度构建器 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); // 按新的cronExpression表达式重新构建trigger trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); //修改map trigger.getJobDataMap().putAll(argMap); // 按新的trigger重新设置job执行 scheduler.rescheduleJob(triggerKey, trigger); } /** * job 更新,只更新更新参数 * * @param jobName 任务名称 * @param jobGroupName 任务所在组名称 * @param argMap 参数 * @throws Exception */ public void updateJob(String jobName, String jobGroupName, Map<String, Object> argMap) throws Exception { TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); //修改map trigger.getJobDataMap().putAll(argMap); // 按新的trigger重新设置job执行 scheduler.rescheduleJob(triggerKey, trigger); } /** * job 删除 * * @param jobName 任务名称 * @param jobGroupName 任务所在组名称 * @throws Exception */ public void deleteJob(String jobName, String jobGroupName) throws Exception { scheduler.pauseTrigger(TriggerKey.triggerKey(jobName, jobGroupName)); scheduler.unscheduleJob(TriggerKey.triggerKey(jobName, jobGroupName)); scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName)); } /** * 启动所有定时任务 */ public void startAllJobs() { try { scheduler.start(); } catch (Exception e) { throw new RuntimeException(e); } } /** * 关闭所有定时任务 */ public void shutdownAllJobs() { try { if (!scheduler.isShutdown()) { scheduler.shutdown(); } } catch (Exception e) { throw new RuntimeException(e); } } /** * 获取所有任务列表 * * @return * @throws SchedulerException */ public List<Map<String, Object>> getAllJob() throws SchedulerException { GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup(); Set<JobKey> jobKeys = scheduler.getJobKeys(matcher); List<Map<String, Object>> jobList = new ArrayList<>(); for (JobKey jobKey : jobKeys) { List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey); for (Trigger trigger : triggers) { Map<String, Object> job = new HashMap<>(); job.put("jobName", jobKey.getName()); job.put("jobGroupName", jobKey.getGroup()); job.put("trigger", trigger.getKey()); Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey()); job.put("jobStatus", triggerState.name()); if (trigger instanceof CronTrigger) { CronTrigger cronTrigger = (CronTrigger) trigger; String cronExpression = cronTrigger.getCronExpression(); job.put("cronExpression", cronExpression); } jobList.add(job); } } return jobList; } }
TestController.java
测试类,类上记得要加@RestController或者方法上加@ResponseBody注解,不然解析参数时会报错:Circular view path [addTasks]: would dispatch back to the current handler URL [/addTasks]]
代码中的map集合存放所需的业务参数package com.quartzs.demo.controller; import com.quartzs.demo.util.DateCronUtil; import com.quartzs.demo.util.QuartzJobManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Date; import java.util.HashMap; import java.util.Map; @RestController public class TestController { @Autowired private QuartzJobManager manager; @RequestMapping("/addTasks") public void addTask(String timeStr) throws Exception { Date date = DateCronUtil.dateTo(timeStr); String s = DateCronUtil.dateToCron(date); Map<String,Object> map = new HashMap<>(); map.put("id","23984901"); manager.addJob("23984901","quartzGroup",s,map); System.out.println("定时任务完成"); } @RequestMapping("/updTasks") public void updTask(String timeStr) throws Exception { Date date = DateCronUtil.dateTo(timeStr); String s = DateCronUtil.dateToCron(date); String id = "23984901"; Map<String,Object> map = new HashMap<>(); map.put("id","xnsdiwnrk"); manager.updateJob(id,"quartzGroup",s,map); System.out.println("定时任务修改完成"); } }
定时任务我需要的是指定时间执行,且只执行一次,我将时间转为cron表达式传入参数中,时间转换工具类在下面
DateCronUtil.javapackage com.quartzs.demo.util; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class DateCronUtil { /** * Date-Cron表达式转化工具类 * Quartz模块使用 * * @author author * 2020-02-11 17:19 */ public static String dateToCron(Date date) { SimpleDateFormat format = new SimpleDateFormat("ss mm HH dd MM ? yyyy"); return format.format(date); } /** * yyyyMMddHHmmss类型转换时间测试用 * @param date * @return */ public static Date dateTo(String date) { SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss"); Date parse =null; try { parse = format.parse(date); } catch (ParseException e) { e.printStackTrace(); } return parse; } }
最后启动类上面加上事务开启的注解,deam完成
DemoApplication.javapackage com.quartzs.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.transaction.annotation.EnableTransactionManagement; @SpringBootApplication @EnableTransactionManagement public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
最后附加quartz建表语句(mysql),需要其他数据库建表语句可以从quartz jar包的org.quartz.impl.jdbcjobstore位置下面下载
-- mysql --- 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)) ENGINE=InnoDB; 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)) 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_CRON_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, CRON_EXPRESSION VARCHAR(120) 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)) 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; CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME); CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME); CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
更多相关内容 -
springboot整合quartz,实现数据库方式执行定时任务
2021-11-19 14:21:48springboot整合quartz,实现数据库方式执行定时任务。把定时任务信息存进数据库,项目启动后自动执行定时任务。 -
springboot整合quartz完整项目
2020-11-02 14:34:36springboot整合quartz完整项目,采用cron类型表达式,可动态添加修改删除定时任务,保证真实可用!可自己扩展 -
Springboot整合quartz产生错误及解决方案
2020-08-19 03:20:22主要介绍了Springboot整合quartz产生错误及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
Springboot整合Quartz实现定时任务数据库动态配置
2021-03-17 15:55:48Springboot整合Quartz实现定时任务数据库动态配置,新增任务、修改cron表达式、暂停任务、恢复任务、删除任务等操作 -
springboot整合Quartz实现动态配置定时任务的方法
2020-08-29 03:56:31本篇文章主要介绍了springboot整合Quartz实现动态配置定时任务的方法,非常具有实用价值,需要的朋友可以参考下 -
springboot整合quartz的demo
2018-01-30 10:36:32springboot整合quartz的demo,实现了simpleTrigger和cronTrigger,具体功能如下:项目启动后,每隔两秒输出一串星号,每隔三秒输出一串#号,访问controller后,添加定时任务,十秒后输出访问者的ip -
springboot整合Quartz实现动态配置定时任务源码
2019-06-10 13:02:40springboot整合Quartz实现动态配置定时任务源码 -
springboot整合quartz定时任务yml文件配置方式
2021-03-23 09:59:45spring-cloud-alibaba+dubbo+nacos+quartz以yml配置方式实现 -
SpringBoot 整合Quartz(集群)实现定时任务调度
2019-02-14 15:02:321、若是部署多台机器,到了时间点,只有一台会执行,其他不会执行。 2、若多个节点其中一个scheduler执行job失败,将会被另外一个scheduler执行 -
SpringBoot整合Quartz
2021-11-02 23:17:07目录`Quartz` 简介 Quartz 简介 Quartz 是一款功能强大的开源任务调度框架,几乎可以集成到任何 Java 应用程序中(小到单机应用,大到分布式应用)。Quartz 可用于创建简单或复杂的任务调度,用以执行数以万计的任务...Quartz
Quartz
简介Quartz
是OpenSymphony
开源组织在Job Scheduling
领域又一个开源项目,是完全由Java
开发的一个开源任务日程管理系统,“任务进度管理器”就是一个在预先确定(被纳入日程)的时间到达时,负责执行(或者通知)其他软件组件的系统。Quartz
是一个开源的作业调度框架,它完全由Java
写成,并设计用于J2SE
和J2EE
应用中,它提供了巨大的灵活性而不牺牲简单性当定时任务愈加复杂时,使用
Spring
注解@Schedule
已经不能满足业务需要在项目开发中,经常需要定时任务来帮助我们来做一些内容,如定时派息、跑批对账、将任务纳入日程或者从日程中取消,开始,停止,暂停日程进度等。
SpringBoot
中现在有两种方案可以选择,第一种是SpringBoot
内置的方式简单注解就可以使用,当然如果需要更复杂的应用场景还是得Quartz
上场,Quartz
目前是Java
体系中最完善的定时方案官方网站:http://quartz-scheduler.org/
Quartz
优点- 丰富的
Job
操作API
- 支持多种配置
SpringBoot
无缝集成- 支持持久化
- 支持集群
Quartz
还支持开源,是一个功能丰富的开源作业调度库,可以集成到几乎任何Java
应用程序中
核心概念
Scheduler
:Quartz
中的任务调度器,通过Trigger
和JobDetail
可以用来调度、暂停和删除任务。调度器就相当于一个容器,装载着任务和触发器,该类是一个接口,代表一个Quartz
的独立运行容器,Trigger
和JobDetail
可以注册到Scheduler
中,两者在Scheduler
中拥有各自的组及名称,组及名称是Scheduler
查找定位容器中某一对象的依据,Trigger
的组及名称必须唯一,JobDetail
的组和名称也必须唯一(但可以和Trigger
的组和名称相同,因为它们是不同类型的)Trigger
:Quartz
中的触发器,是一个类,描述触发Job
执行的时间触发规则,主要有SimpleTrigger
和CronTrigger
这两个子类。当且仅当需调度一次或者以固定时间间隔周期执行调度,SimpleTrigger
是最适合的选择;而CronTrigger
则可以通过Cron
表达式定义出各种复杂时间规则的调度方案:如工作日周一到周五的15:00 ~ 16:00
执行调度等JobDetail
:Quartz
中需要执行的任务详情,包括了任务的唯一标识和具体要执行的任务,可以通过JobDataMap
往任务中传递数据Job
:Quartz
中具体的任务,包含了执行任务的具体方法。是一个接口,只定义一个方法execute()
方法,在实现接口的execute()
方法中编写所需要定时执行的Job
当然可以这样快速理解:
job
:任务 - 你要做什么事Trigger
:触发器 - 你什么时候去做Scheduler
:任务调度 - 你什么时候需要做什么事
四者其关系如下图所示
Job
为作业的接口,为任务调度的对象;JobDetail
用来描述Job
的实现类及其他相关的静态信息;Trigger
做为作业的定时管理工具,一个Trigger
只能对应一个作业实例,而一个作业实例可对应多个触发器;Scheduler
做为定时任务容器,是Quartz
最上层的东西,它提携了所有触发器和作业,使它们协调工作,每个Scheduler
都存有JobDetail
和Trigger
的注册,一个Scheduler
中可以注册多个JobDetail
和多个Trigger
Quartz
的作业存储类型RAMJobStore
:RAM
也就是内存,默认情况下 Quartz 会将任务调度存储在内存中
,这种方式性能是最好的,因为内存的速度是最快的。不好的地方就是数据缺乏持久性,但程序崩溃或者重新发布的时候,所有运行信息都会丢失JDBC
作业存储:存到数据库之后,可以做单点也可以做集群,当任务多了之后,可以统一进行管理,随时停止、暂停、修改任务。关闭或者重启服务器,运行的信息都不会丢失。缺点就是运行速度快慢取决于连接数据库的快慢
Cron
表达式Cron
表达式是一个字符串,包括6~7
个时间元素,在Quartz
中可以用于指定任务的执行时间Cron
语法Seconds Minutes Hours DayofMonth Month DayofWeek 秒 分钟 小时 日期天/日 日期月份 星期
Cron
语法中每个时间元素的说明时间元素 可出现的字符 有效数值范围 Seconds , - * / 0-59 Minutes , - * / 0-59 Hours , - * / 0-23 DayofMonth , - * / ? L W 0-31 Month , - * / 1-12 DayofWeek , - * / ? L # 1-7或SUN-SAT Cron
语法中特殊字符说明字符 作用 举例 , 列出枚举值 在Minutes域使用5,10,表示在5分和10分各触发一次 - 表示触发范围 在Minutes域使用5-10,表示从5分到10分钟每分钟触发一次 * 匹配任意值 在Minutes域使用*, 表示每分钟都会触发一次 / 起始时间开始触发,每隔固定时间触发一次 在Minutes域使用5/10,表示5分时触发一次,每10分钟再触发一次 ? 在DayofMonth和DayofWeek中,用于匹配任意值 在DayofMonth域使用?,表示每天都触发一次 # 在DayofMonth中,确定第几个星期几 1#3表示第三个星期日 L 表示最后 在DayofWeek中使用5L,表示在最后一个星期四触发 W 表示有效工作日(周一到周五) 在DayofMonth使用5W,如果5日是星期六,则将在最近的工作日4日触发一次 在线
Cron
表达式生成器其实
Cron
表达式无需多记,需要使用的时候直接使用在线生成器就可以了,地址:https://cron.qqe2.com/Springboot
整合Quartz
SpringBoot
版本:2.0.9.RELEASE
MySQL
版本:5.7.35
数据库表准备
Quartz
存储任务信息有两种方式,使用内存或者使用数据库来存储,这里我们采用MySQL
数据库存储的方式,首先需要新建Quartz
的相关表,sql
脚本下载地址:http://www.quartz-scheduler.org/downloads/,名称为tables_mysql.sql
,创建成功后数据库中多出11
张表Maven
主要依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> <!--mysql 驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <!--pagehelper分页--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency>
这里使用
druid
作为数据库连接池,Quartz
默认使用c3p0
配置文件
quartz.properties
默认情况下,
Quartz
会加载classpath
下的quartz.properties
作为配置文件。如果找不到,则会使用quartz
框架自己jar
包下org/quartz
底下的quartz.properties
文件#主要分为scheduler、threadPool、jobStore、dataSource等部分 org.quartz.scheduler.instanceId=AUTO org.quartz.scheduler.instanceName=DefaultQuartzScheduler #如果您希望Quartz Scheduler通过RMI作为服务器导出本身,则将“rmi.export”标志设置为true #在同一个配置文件中为'org.quartz.scheduler.rmi.export'和'org.quartz.scheduler.rmi.proxy'指定一个'true'值是没有意义的,如果你这样做'export'选项将被忽略 org.quartz.scheduler.rmi.export=false #如果要连接(使用)远程服务的调度程序,则将“org.quartz.scheduler.rmi.proxy”标志设置为true。您还必须指定RMI注册表进程的主机和端口 - 通常是“localhost”端口1099 org.quartz.scheduler.rmi.proxy=false org.quartz.scheduler.wrapJobExecutionInUserTransaction=false #实例化ThreadPool时,使用的线程类为SimpleThreadPool org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool #threadCount和threadPriority将以setter的形式注入ThreadPool实例 #并发个数 如果你只有几个工作每天触发几次 那么1个线程就可以,如果你有成千上万的工作,每分钟都有很多工作 那么久需要50-100之间. #只有1到100之间的数字是非常实用的 org.quartz.threadPool.threadCount=5 #优先级 默认值为5 org.quartz.threadPool.threadPriority=5 #可以是“true”或“false”,默认为false org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true #在被认为“misfired”(失火)之前,调度程序将“tolerate(容忍)”一个Triggers(触发器)将其下一个启动时间通过的毫秒数。默认值(如果您在配置中未输入此属性)为60000(60秒) org.quartz.jobStore.misfireThreshold=5000 # 默认存储在内存中,RAMJobStore快速轻便,但是当进程终止时,所有调度信息都会丢失 #org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore #持久化方式,默认存储在内存中,此处使用数据库方式 org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX #您需要为JobStore选择一个DriverDelegate才能使用。DriverDelegate负责执行特定数据库可能需要的任何JDBC工作 # StdJDBCDelegate是一个使用“vanilla”JDBC代码(和SQL语句)来执行其工作的委托,用于完全符合JDBC的驱动程序 org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate #可以将“org.quartz.jobStore.useProperties”配置参数设置为“true”(默认为false),以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串, #因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题 org.quartz.jobStore.useProperties=true #表前缀 org.quartz.jobStore.tablePrefix=QRTZ_ #数据源别名,自定义 org.quartz.jobStore.dataSource=qzDS #使用阿里的druid作为数据库连接池 org.quartz.dataSource.qzDS.connectionProvider.class=org.example.config.DruidPoolingconnectionProvider org.quartz.dataSource.qzDS.URL=jdbc:mysql://127.0.0.1:3306/test_quartz?characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=UTC org.quartz.dataSource.qzDS.user=root org.quartz.dataSource.qzDS.password=123456 org.quartz.dataSource.qzDS.driver=com.mysql.jdbc.Driver org.quartz.dataSource.qzDS.maxConnections=10 #设置为“true”以打开群集功能。如果您有多个Quartz实例使用同一组数据库表,则此属性必须设置为“true”,否则您将遇到破坏 #org.quartz.jobStore.isClustered=false
关于配置详细解释:https://blog.csdn.net/zixiao217/article/details/53091812
也可以查看官网:http://www.quartz-scheduler.org/documentation/2.3.1-SNAPSHOT/
application.properties
server.port=8080 #JDBC 配置 spring.datasource.druid.url=jdbc:mysql://127.0.0.1:3306/test_quartz?characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=UTC spring.datasource.druid.username=root spring.datasource.druid.password=123456 spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver spring.datasource.type=com.alibaba.druid.pool.DruidDataSource #druid 连接池配置 spring.datasource.druid.initial-size=3 spring.datasource.druid.min-idle=3 spring.datasource.druid.max-active=10 spring.datasource.druid.max-wait=60000 #指定 mapper 文件路径 mybatis.mapper-locations=classpath:org/example/mapper/*.xml mybatis.configuration.cache-enabled=true #开启驼峰命名 mybatis.configuration.map-underscore-to-camel-case=true #打印 SQL 语句 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
quartz
配置类QuartzConfig
@Configuration public class QuartzConfig implements SchedulerFactoryBeanCustomizer { @Bean public Properties properties() throws IOException { PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); // 对quartz.properties文件进行读取 propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties")); // 在quartz.properties中的属性被读取并注入后再初始化对象 propertiesFactoryBean.afterPropertiesSet(); return propertiesFactoryBean.getObject(); } @Bean public SchedulerFactoryBean schedulerFactoryBean() throws IOException { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); schedulerFactoryBean.setQuartzProperties(properties()); return schedulerFactoryBean; } /* * quartz初始化监听器 */ @Bean public QuartzInitializerListener executorListener() { return new QuartzInitializerListener(); } /* * 通过SchedulerFactoryBean获取Scheduler的实例 */ @Bean public Scheduler scheduler() throws IOException { return schedulerFactoryBean().getScheduler(); } /** * 使用阿里的druid作为数据库连接池 */ @Override public void customize(@NotNull SchedulerFactoryBean schedulerFactoryBean) { schedulerFactoryBean.setStartupDelay(2); schedulerFactoryBean.setAutoStartup(true); schedulerFactoryBean.setOverwriteExistingJobs(true); } }
创建任务类
HelloJob
@Slf4j public class HelloJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) { QuartzService quartzService = (QuartzService) SpringUtil.getBean("quartzServiceImpl"); PageInfo<JobAndTriggerDto> jobAndTriggerDetails = quartzService.getJobAndTriggerDetails(1, 10); log.info("任务列表总数为:" + jobAndTriggerDetails.getTotal()); log.info("Hello Job执行时间: " + DateUtil.now()); } }
业务
Service
层具体的
QuartzService
接口这里不在赘述,可以查看后面的源码@Slf4j @Service public class QuartzServiceImpl implements QuartzService { @Autowired private JobDetailMapper jobDetailMapper; @Autowired private Scheduler scheduler; @Override public PageInfo<JobAndTriggerDto> getJobAndTriggerDetails(Integer pageNum, Integer pageSize) { PageHelper.startPage(pageNum, pageSize); List<JobAndTriggerDto> list = jobDetailMapper.getJobAndTriggerDetails(); PageInfo<JobAndTriggerDto> pageInfo = new PageInfo<>(list); return pageInfo; } /** * 新增定时任务 * * @param jName 任务名称 * @param jGroup 任务组 * @param tName 触发器名称 * @param tGroup 触发器组 * @param cron cron表达式 */ @Override public void addjob(String jName, String jGroup, String tName, String tGroup, String cron) { try { // 构建JobDetail JobDetail jobDetail = JobBuilder.newJob(HelloJob.class) .withIdentity(jName, jGroup) .build(); // 按新的cronExpression表达式构建一个新的trigger CronTrigger trigger = TriggerBuilder.newTrigger() .withIdentity(tName, tGroup) .startNow() .withSchedule(CronScheduleBuilder.cronSchedule(cron)) .build(); // 启动调度器 scheduler.start(); scheduler.scheduleJob(jobDetail, trigger); } catch (Exception e) { log.info("创建定时任务失败" + e); } } @Override public void pausejob(String jName, String jGroup) throws SchedulerException { scheduler.pauseJob(JobKey.jobKey(jName, jGroup)); } @Override public void resumejob(String jName, String jGroup) throws SchedulerException { scheduler.resumeJob(JobKey.jobKey(jName, jGroup)); } @Override public void rescheduleJob(String jName, String jGroup, String cron) throws SchedulerException { TriggerKey triggerKey = TriggerKey.triggerKey(jName, jGroup); // 表达式调度构建器 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); // 按新的cronExpression表达式重新构建trigger trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); // 按新的trigger重新设置job执行,重启触发器 scheduler.rescheduleJob(triggerKey, trigger); } @Override public void deletejob(String jName, String jGroup) throws SchedulerException { scheduler.pauseTrigger(TriggerKey.triggerKey(jName, jGroup)); scheduler.unscheduleJob(TriggerKey.triggerKey(jName, jGroup)); scheduler.deleteJob(JobKey.jobKey(jName, jGroup)); } }
Controller
层@Slf4j @Controller @RequestMapping(path = "/quartz") public class QuartzController { @Autowired private QuartzService quartzService; /** * 新增定时任务 * * @param jName 任务名称 * @param jGroup 任务组 * @param tName 触发器名称 * @param tGroup 触发器组 * @param cron cron表达式 * @return ResultMap */ @PostMapping(path = "/addjob") @ResponseBody public ResultMap addjob(String jName, String jGroup, String tName, String tGroup, String cron) { try { quartzService.addjob(jName, jGroup, tName, tGroup, cron); return new ResultMap().success().message("添加任务成功"); } catch (Exception e) { e.printStackTrace(); return new ResultMap().error().message("添加任务失败"); } } /** * 暂停任务 * * @param jName 任务名称 * @param jGroup 任务组 * @return ResultMap */ @PostMapping(path = "/pausejob") @ResponseBody public ResultMap pausejob(String jName, String jGroup) { try { quartzService.pausejob(jName, jGroup); return new ResultMap().success().message("暂停任务成功"); } catch (SchedulerException e) { e.printStackTrace(); return new ResultMap().error().message("暂停任务失败"); } } /** * 恢复任务 * * @param jName 任务名称 * @param jGroup 任务组 * @return ResultMap */ @PostMapping(path = "/resumejob") @ResponseBody public ResultMap resumejob(String jName, String jGroup) { try { quartzService.resumejob(jName, jGroup); return new ResultMap().success().message("恢复任务成功"); } catch (SchedulerException e) { e.printStackTrace(); return new ResultMap().error().message("恢复任务失败"); } } /** * 重启任务 * * @param jName 任务名称 * @param jGroup 任务组 * @param cron cron表达式 * @return ResultMap */ @PostMapping(path = "/reschedulejob") @ResponseBody public ResultMap rescheduleJob(String jName, String jGroup, String cron) { try { quartzService.rescheduleJob(jName, jGroup, cron); return new ResultMap().success().message("重启任务成功"); } catch (SchedulerException e) { e.printStackTrace(); return new ResultMap().error().message("重启任务失败"); } } /** * 删除任务 * * @param jName 任务名称 * @param jGroup 任务组 * @return ResultMap */ @PostMapping(path = "/deletejob") @ResponseBody public ResultMap deletejob(String jName, String jGroup) { try { quartzService.deletejob(jName, jGroup); return new ResultMap().success().message("删除任务成功"); } catch (SchedulerException e) { e.printStackTrace(); return new ResultMap().error().message("删除任务失败"); } } /** * 查询任务 * * @param pageNum 页码 * @param pageSize 每页显示多少条数据 * @return Map */ @GetMapping(path = "/queryjob") @ResponseBody public ResultMap queryjob(Integer pageNum, Integer pageSize) { PageInfo<JobAndTriggerDto> pageInfo = quartzService.getJobAndTriggerDetails(pageNum, pageSize); Map<String, Object> map = new HashMap<>(); if (!StringUtils.isEmpty(pageInfo.getTotal())) { map.put("JobAndTrigger", pageInfo); map.put("number", pageInfo.getTotal()); return new ResultMap().success().data(map).message("查询任务成功"); } return new ResultMap().fail().message("查询任务成功失败,没有数据"); } }
接口测试
新增定时任务
postman
测试如下
数据库数据展示如下
同样,我们的任务类HelloJob
也开始执行了,控制台日志如下停止项目,再启动运行
可以看到项目中
HelloJob
的任务依然在运行,这就是quartz
数据库持久化的好处 - 丰富的
-
springboot 整合 quartz
2022-04-21 11:40:35quartz-project: quartz 2. 环境信息 springboot 2.5.4 3. 搭建流程 3.1 创建一个springboot项目 (略) 3.2 pom.xml <dependencies> <!-- springboot启动文件 --> <dependency> <...1. 示例代码 :
2. 环境信息
springboot 2.5.4
3. 搭建流程
3.1 创建一个springboot项目 (略)
3.2 pom.xml
<dependencies> <!-- springboot启动文件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--druid数据库连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.20</version> </dependency> <!--mybatisplus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.2</version> </dependency> <!--mybatisplus 代码生成器--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.4.1</version> </dependency> <!--hutool工具类--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.3.4</version> </dependency> <!--hutool 表情类扩展包--> <dependency> <groupId>com.vdurmont</groupId> <artifactId>emoji-java</artifactId> <version>4.0.0</version> </dependency> </dependencies>
application.yml
server: port: 9999 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver password: 123456 url: jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&allowMutiQueries=true username: root # 配置数据库连接池 type: com.alibaba.druid.pool.DruidDataSource druid: initial-size: 1 min-idle: 1 max-active: 20 max-wait: 60000 test-while-idle: true time-between-connect-error-millis: 60000 min-evictable-idle-time-millis: 30000 validation-query: select 'x' test-on-borrow: false test-on-return: false pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 use-global-data-source-stat: false filters: stat,wall,slf4j connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 time-between-log-stats-millis: 300000 username: ${spring.datasource.username} password: ${spring.datasource.password} url : ${spring.datasource.url} driver-class-name: ${spring.datasource.driver-class-name}
quartz.properties
#主要分为scheduler、threadPool、jobStore、dataSource等部分 org.quartz.scheduler.instanceId=AUTO #如果您希望Quartz Scheduler通过RMI作为服务器导出本身,则将“rmi.export”标志设置为true #在同一个配置文件中为'org.quartz.scheduler.rmi.export'和'org.quartz.scheduler.rmi.proxy'指定一个'true'值是没有意义的,如果你这样做'export'选项将被忽略 org.quartz.scheduler.rmi.export=false #如果要连接(使用)远程服务的调度程序,则将“org.quartz.scheduler.rmi.proxy”标志设置为true。您还必须指定RMI注册表进程的主机和端口 - 通常是“localhost”端口1099 org.quartz.scheduler.rmi.proxy=false org.quartz.scheduler.wrapJobExecutionInUserTransaction=false #实例化ThreadPool时,使用的线程类为SimpleThreadPool org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool #threadCount和threadPriority将以setter的形式注入ThreadPool实例 #并发个数 如果你只有几个工作每天触发几次 那么1个线程就可以,如果你有成千上万的工作,每分钟都有很多工作 那么久需要50-100之间. #只有1到100之间的数字是非常实用的 org.quartz.threadPool.threadCount=5 #优先级 默认值为5 org.quartz.threadPool.threadPriority=5 #可以是“true”或“false”,默认为false org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true #在被认为“misfired”(失火)之前,调度程序将“tolerate(容忍)”一个Triggers(触发器)将其下一个启动时间通过的毫秒数。默认值(如果您在配置中未输入此属性)为60000(60秒) org.quartz.jobStore.misfireThreshold=5000 # 默认存储在内存中,RAMJobStore快速轻便,但是当进程终止时,所有调度信息都会丢失 #org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore #持久化方式,默认存储在内存中,此处使用数据库方式 org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX #您需要为JobStore选择一个DriverDelegate才能使用。DriverDelegate负责执行特定数据库可能需要的任何JDBC工作 # StdJDBCDelegate是一个使用“vanilla”JDBC代码(和SQL语句)来执行其工作的委托,用于完全符合JDBC的驱动程序 org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate #可以将“org.quartz.jobStore.useProperties”配置参数设置为“true”(默认为false),以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串, #因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题 org.quartz.jobStore.useProperties=true #表前缀 org.quartz.jobStore.tablePrefix=QRTZ_ #数据源别名,自定义 org.quartz.jobStore.dataSource=qzDS #使用阿里的druid作为数据库连接池 org.quartz.dataSource.qzDS.connectionProvider.class=com.hctrl.quartz.config.DruidConnectionProvider org.quartz.dataSource.qzDS.URL=jdbc:mysql://127.0.0.1:3306/quartz?characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=UTC org.quartz.dataSource.qzDS.user=root org.quartz.dataSource.qzDS.password=123456 org.quartz.dataSource.qzDS.driver=com.mysql.cj.jdbc.Driver org.quartz.dataSource.qzDS.maxConnection=10 #设置为“true”以打开群集功能。如果您有多个Quartz实例使用同一组数据库表,则此属性必须设置为“true”,否则您将遇到破坏 #org.quartz.jobStore.isClustered=false
quartz.sql (项目启动不会自动创建表, 需要在数据库中手动创建表)
/* Navicat Premium Data Transfer Source Server : localhost Source Server Type : MySQL Source Server Version : 80025 Source Host : localhost:3306 Source Schema : quartz Target Server Type : MySQL Target Server Version : 80025 File Encoding : 65001 Date: 21/04/2022 11:17:50 */ 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) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '计划名', `trigger_name` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '触发器名称', `trigger_group` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '触发器组', `blob_data` blob, PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, INDEX `sched_name`(`sched_name`) USING BTREE, CONSTRAINT `qrtz_blob_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '以blob 类型存储的触发器' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for qrtz_calendars -- ---------------------------- DROP TABLE IF EXISTS `qrtz_calendars`; CREATE TABLE `qrtz_calendars` ( `sched_name` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '计划名称', `calendar_name` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `calendar` blob NOT NULL, PRIMARY KEY (`sched_name`, `calendar_name`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '日历信息表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for qrtz_cron_triggers -- ---------------------------- DROP TABLE IF EXISTS `qrtz_cron_triggers`; CREATE TABLE `qrtz_cron_triggers` ( `sched_name` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '计划名称', `trigger_name` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '触发器名称', `trigger_group` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '触发器组', `cron_expression` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '时间表达式', `time_zone_id` varchar(80) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '时区id', PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, CONSTRAINT `qrtz_cron_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '定时触发器表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for qrtz_fired_triggers -- ---------------------------- DROP TABLE IF EXISTS `qrtz_fired_triggers`; CREATE TABLE `qrtz_fired_triggers` ( `sched_name` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '计划名称', `entry_id` varchar(95) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '组标识', `trigger_name` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '触发器名称', `trigger_group` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '触发器组', `instance_name` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '当前实例的名称', `fired_time` bigint(0) NOT NULL COMMENT '当前执行时间', `sched_time` bigint(0) NOT NULL COMMENT '计划时间', `priority` int(0) NOT NULL COMMENT '权重', `state` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '状态', `job_name` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '作业名称', `job_group` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '作业组', `is_nonconcurrent` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '是否并行', `requests_recovery` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '是否要求唤醒', PRIMARY KEY (`sched_name`, `entry_id`) USING BTREE, INDEX `idx_qrtz_ft_trig_inst_name`(`sched_name`, `instance_name`) USING BTREE, INDEX `idx_qrtz_ft_inst_job_req_rcvry`(`sched_name`, `instance_name`, `requests_recovery`) USING BTREE, INDEX `idx_qrtz_ft_j_g`(`sched_name`, `job_name`, `job_group`) USING BTREE, INDEX `idx_qrtz_ft_jg`(`sched_name`, `job_group`) USING BTREE, INDEX `idx_qrtz_ft_t_g`(`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, INDEX `idx_qrtz_ft_tg`(`sched_name`, `trigger_group`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '保存已经触发的触发器状态信息' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for qrtz_job_details -- ---------------------------- DROP TABLE IF EXISTS `qrtz_job_details`; CREATE TABLE `qrtz_job_details` ( `sched_name` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '计划名称', `job_name` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '作业名称', `job_group` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '作业组', `description` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '描述', `job_class_name` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '作业程序类名', `is_durable` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '是否持久', `is_nonconcurrent` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '是否并行', `is_update_data` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '是否更新', `requests_recovery` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '是否要求唤醒', `job_data` blob COMMENT '作业名称', PRIMARY KEY (`sched_name`, `job_name`, `job_group`) USING BTREE, INDEX `idx_qrtz_j_req_recovery`(`sched_name`, `requests_recovery`) USING BTREE, INDEX `idx_qrtz_j_grp`(`sched_name`, `job_group`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'job 详细信息表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for qrtz_locks -- ---------------------------- DROP TABLE IF EXISTS `qrtz_locks`; CREATE TABLE `qrtz_locks` ( `sched_name` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '计划名称', `lock_name` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '锁名称', PRIMARY KEY (`sched_name`, `lock_name`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '存储程序的悲观锁的信息(假如使用了悲观锁) ' ROW_FORMAT = Dynamic; -- ---------------------------- -- 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) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '计划名称', `trigger_group` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '触发器组', PRIMARY KEY (`sched_name`, `trigger_group`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '存放暂停掉的触发器表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for qrtz_scheduler_state -- ---------------------------- DROP TABLE IF EXISTS `qrtz_scheduler_state`; CREATE TABLE `qrtz_scheduler_state` ( `sched_name` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '计划名称', `instance_name` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '实例名称', `last_checkin_time` bigint(0) NOT NULL COMMENT '最后的检查时间', `checkin_interval` bigint(0) NOT NULL COMMENT '检查间隔', PRIMARY KEY (`sched_name`, `instance_name`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '调度器状态表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for qrtz_simple_triggers -- ---------------------------- DROP TABLE IF EXISTS `qrtz_simple_triggers`; CREATE TABLE `qrtz_simple_triggers` ( `sched_name` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '计划名称', `trigger_name` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '触发器名称', `trigger_group` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '触发器组', `repeat_count` bigint(0) NOT NULL COMMENT '重复次数', `repeat_interval` bigint(0) NOT NULL COMMENT '重复间隔', `times_triggered` bigint(0) NOT NULL COMMENT '触发次数', PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, CONSTRAINT `qrtz_simple_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '简单的触发器表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for qrtz_simprop_triggers -- ---------------------------- DROP TABLE IF EXISTS `qrtz_simprop_triggers`; CREATE TABLE `qrtz_simprop_triggers` ( `sched_name` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '计划名称', `trigger_name` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '触发器名称', `trigger_group` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '触发器组', `str_prop_1` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '计划名称', `str_prop_2` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '计划名称', `str_prop_3` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '计划名称', `int_prop_1` int(0) DEFAULT NULL, `int_prop_2` int(0) DEFAULT NULL, `long_prop_1` bigint(0) DEFAULT NULL, `long_prop_2` bigint(0) DEFAULT NULL, `dec_prop_1` decimal(13, 4) DEFAULT NULL, `dec_prop_2` decimal(13, 4) DEFAULT NULL, `bool_prop_1` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, `bool_prop_2` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, CONSTRAINT `qrtz_simprop_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '存储两种类型的触发器表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for qrtz_triggers -- ---------------------------- DROP TABLE IF EXISTS `qrtz_triggers`; CREATE TABLE `qrtz_triggers` ( `sched_name` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '计划名称', `trigger_name` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '触发器名称', `trigger_group` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '触发器组', `job_name` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '作业名称', `job_group` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '作业组', `description` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '描述', `next_fire_time` bigint(0) DEFAULT NULL COMMENT '下次执行时间', `prev_fire_time` bigint(0) DEFAULT NULL COMMENT '前一次', `priority` int(0) DEFAULT NULL COMMENT '优先权', `trigger_state` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '触发器状态', `trigger_type` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '触发器类型', `start_time` bigint(0) NOT NULL COMMENT '开始时间', `end_time` bigint(0) DEFAULT NULL COMMENT '结束时间', `calendar_name` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '日历名称', `misfire_instr` smallint(0) DEFAULT NULL COMMENT '失败次数', `job_data` blob COMMENT '作业数据', PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, INDEX `idx_qrtz_t_j`(`sched_name`, `job_name`, `job_group`) USING BTREE, INDEX `idx_qrtz_t_jg`(`sched_name`, `job_group`) USING BTREE, INDEX `idx_qrtz_t_c`(`sched_name`, `calendar_name`) USING BTREE, INDEX `idx_qrtz_t_g`(`sched_name`, `trigger_group`) USING BTREE, INDEX `idx_qrtz_t_state`(`sched_name`, `trigger_state`) USING BTREE, INDEX `idx_qrtz_t_n_state`(`sched_name`, `trigger_name`, `trigger_group`, `trigger_state`) USING BTREE, INDEX `idx_qrtz_t_n_g_state`(`sched_name`, `trigger_group`, `trigger_state`) USING BTREE, INDEX `idx_qrtz_t_next_fire_time`(`sched_name`, `next_fire_time`) USING BTREE, INDEX `idx_qrtz_t_nft_st`(`sched_name`, `trigger_state`, `next_fire_time`) USING BTREE, INDEX `idx_qrtz_t_nft_misfire`(`sched_name`, `misfire_instr`, `next_fire_time`) USING BTREE, INDEX `idx_qrtz_t_nft_st_misfire`(`sched_name`, `misfire_instr`, `next_fire_time`, `trigger_state`) USING BTREE, INDEX `idx_qrtz_t_nft_st_misfire_grp`(`sched_name`, `misfire_instr`, `next_fire_time`, `trigger_group`, `trigger_state`) USING BTREE, CONSTRAINT `qrtz_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `job_name`, `job_group`) REFERENCES `qrtz_job_details` (`sched_name`, `job_name`, `job_group`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '触发器表' ROW_FORMAT = Dynamic; SET FOREIGN_KEY_CHECKS = 1;
3.3 代码
DruidConfig : druid数据源配置
package com.hctrl.quartz.config; import com.alibaba.druid.pool.DruidDataSource; import com.hctrl.quartz.properties.DruidDataSourceProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; import java.sql.SQLException; /** * @author hanchao * @version 1.0 * @date 2022/4/21 9:54 */ @Configuration @EnableConfigurationProperties({ DruidDataSourceProperties.class }) public class DruidConfig { /** * 此处bean name设置为druidDataSource1 , 防止冲突 * @param properties * @return */ @Bean @ConditionalOnMissingBean public DataSource druidDataSource1(DruidDataSourceProperties properties) { DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(properties.getDriverClassName()); druidDataSource.setUrl(properties.getUrl()); druidDataSource.setUsername(properties.getUsername()); druidDataSource.setPassword(properties.getPassword()); druidDataSource.setInitialSize(properties.getInitialSize()); druidDataSource.setMinIdle(properties.getMinIdle()); druidDataSource.setMaxActive(properties.getMaxActive()); druidDataSource.setMaxWait(properties.getMaxWait()); druidDataSource.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRunsMillis()); druidDataSource.setMinEvictableIdleTimeMillis(properties.getMinEvictableIdleTimeMillis()); druidDataSource.setValidationQuery(properties.getValidationQuery()); druidDataSource.setTestWhileIdle(properties.isTestWhileIdle()); druidDataSource.setTestOnBorrow(properties.isTestOnBorrow()); druidDataSource.setTestOnReturn(properties.isTestOnReturn()); druidDataSource.setPoolPreparedStatements(properties.isPoolPreparedStatements()); druidDataSource.setMaxPoolPreparedStatementPerConnectionSize( properties.getMaxPoolPreparedStatementPerConnectionSize()); try { druidDataSource.init(); } catch (SQLException e) { e.printStackTrace(); } return druidDataSource; } }
DruidConnectionProvider : 将quartz数据源修改为druid, 类中的属性会自动读取配置文件, 写好get, set方法即可
package com.hctrl.quartz.config; import com.alibaba.druid.pool.DruidDataSource; import org.quartz.SchedulerException; import org.quartz.utils.ConnectionProvider; import java.sql.Connection; import java.sql.SQLException; /** * @author hanchao * @version 1.0 * @date 2022/4/21 9:37 */ public class DruidConnectionProvider implements ConnectionProvider { //JDBC驱动 public String driver; //JDBC连接串 public String URL; //数据库用户名 public String user; //数据库用户密码 public String password; //数据库最大连接数 public int maxConnection; //数据库SQL查询每次连接返回执行到连接池,以确保它仍然是有效的。 public String validationQuery; private boolean validateOnCheckout; private int idleConnectionValidationSeconds; public String maxCachedStatementsPerConnection; private String discardIdleConnectionsSeconds; public static final int DEFAULT_DB_MAX_CONNECTIONS = 10; public static final int DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION = 120; private DruidDataSource datasource; public Connection getConnection() throws SQLException { return datasource.getConnection(); } public void shutdown() throws SQLException { datasource.close(); } public void initialize() throws SQLException{ if (this.URL == null) { throw new SQLException("DBPool could not be created: DB URL cannot be null"); } if (this.driver == null) { throw new SQLException("DBPool driver could not be created: DB driver class name cannot be null!"); } if (this.maxConnection < 0) { throw new SQLException("DBPool maxConnectins could not be created: Max connections must be greater than zero!"); } datasource = new DruidDataSource(); try{ datasource.setDriverClassName(this.driver); } catch (Exception e) { try { throw new SchedulerException("Problem setting driver class name on datasource: " + e.getMessage(), e); } catch (SchedulerException e1) { } } datasource.setUrl(this.URL); datasource.setUsername(this.user); datasource.setPassword(this.password); datasource.setMaxActive(this.maxConnection); datasource.setMinIdle(1); datasource.setMaxWait(0); datasource.setMaxPoolPreparedStatementPerConnectionSize(this.DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION); if (this.validationQuery != null) { datasource.setValidationQuery(this.validationQuery); if(!this.validateOnCheckout) datasource.setTestOnReturn(true); else datasource.setTestOnBorrow(true); datasource.setValidationQueryTimeout(this.idleConnectionValidationSeconds); } } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 提供get set方法 * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public String getDriver() { return driver; } public void setDriver(String driver) { this.driver = driver; } public String getURL() { return URL; } public void setURL(String URL) { this.URL = URL; } public String getUser() { return user; } public void setUser(String user) { this.user = user; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getMaxConnection() { return maxConnection; } public void setMaxConnection(int maxConnection) { this.maxConnection = maxConnection; } public String getValidationQuery() { return validationQuery; } public void setValidationQuery(String validationQuery) { this.validationQuery = validationQuery; } public boolean isValidateOnCheckout() { return validateOnCheckout; } public void setValidateOnCheckout(boolean validateOnCheckout) { this.validateOnCheckout = validateOnCheckout; } public int getIdleConnectionValidationSeconds() { return idleConnectionValidationSeconds; } public void setIdleConnectionValidationSeconds(int idleConnectionValidationSeconds) { this.idleConnectionValidationSeconds = idleConnectionValidationSeconds; } public DruidDataSource getDatasource() { return datasource; } public void setDatasource(DruidDataSource datasource) { this.datasource = datasource; } }
QuartzConfig : quartz配置类
package com.hctrl.quartz.config; import org.quartz.Scheduler; import org.quartz.ee.servlet.QuartzInitializerListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import javax.annotation.Resource; import javax.sql.DataSource; import java.io.IOException; import java.util.Properties; /** * @author hanchao * @version 1.0 * @date 2022/4/21 9:34 */ @Configuration public class QuartzConfig implements SchedulerFactoryBeanCustomizer { @Resource private DataSource druidDataSource1; @Bean public Properties properties() throws IOException { PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); // 对quartz.properties文件进行读取 propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties")); // 在quartz.properties中的属性被读取并注入后再初始化对象 propertiesFactoryBean.afterPropertiesSet(); return propertiesFactoryBean.getObject(); } @Bean public SchedulerFactoryBean schedulerFactoryBean() throws IOException { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); // schedulerFactoryBean.setQuartzProperties(properties()); //此处一定要配置数据源, 否则创建的任务和触发器不用存入到数据库中 schedulerFactoryBean.setDataSource(druidDataSource1); return schedulerFactoryBean; } /* * quartz初始化监听器 */ @Bean public QuartzInitializerListener executorListener() { return new QuartzInitializerListener(); } /* * 通过SchedulerFactoryBean获取Scheduler的实例 */ @Bean public Scheduler scheduler() throws IOException { return schedulerFactoryBean().getScheduler(); } /** * 使用阿里的druid作为数据库连接池 */ @Override public void customize(SchedulerFactoryBean schedulerFactoryBean) { schedulerFactoryBean.setStartupDelay(2); schedulerFactoryBean.setAutoStartup(true); schedulerFactoryBean.setOverwriteExistingJobs(true); } }
DruidDataSourceProperties : 读取配置文件的配置信息, 创建druid数据源使用
package com.hctrl.quartz.properties; import lombok.Data; import lombok.extern.apachecommons.CommonsLog; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * @author hanchao * @version 1.0 * @date 2022/4/21 9:52 */ @ConfigurationProperties(prefix="spring.datasource.druid") @Data public class DruidDataSourceProperties { private String driverClassName; private String url; private String username; private String password; // jdbc connection pool private int initialSize; private int minIdle; private int maxActive = 100; private long maxWait; private long timeBetweenEvictionRunsMillis; private long minEvictableIdleTimeMillis; private String validationQuery; private boolean testWhileIdle; private boolean testOnBorrow; private boolean testOnReturn; private boolean poolPreparedStatements; private int maxPoolPreparedStatementPerConnectionSize; // filter private String filters; }
controller :
package com.hctrl.quartz.controller; import cn.hutool.cron.CronUtil; import cn.hutool.cron.pattern.CronPattern; import cn.hutool.cron.pattern.CronPatternUtil; import com.hctrl.quartz.service.QuartzService; import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import result.BaseResult; import result.CommonResult; /** * * * 每个任务JobDetail可以绑定多个Trigger,但一个Trigger只能绑定一个任务 * * @author hanchao * @version 1.0 * @date 2022/4/21 9:29 */ @RestController @RequestMapping("/v1/quartz") public class QuartzController { @Autowired private QuartzService quartzService; /** * 新增定时任务 * * @param jName 任务名称 * @param jGroup 任务组 * @param tName 触发器名称 * @param tGroup 触发器组 * @param cron cron表达式 * @return ResultMap */ @PostMapping(path = "/addJob") public BaseResult addJob(String jName, String jGroup, String tName, String tGroup, String cron) { try { if (cron == null){ cron = "*/5 * * * * ?"; } quartzService.addJob(jName, jGroup, tName, tGroup, cron); return CommonResult.buildSuccess("添加任务成功"); } catch (Exception e) { e.printStackTrace(); return CommonResult.buildFailure(500,"添加任务失败"); } } /** * 暂停任务 * * @param jName 任务名称 * @param jGroup 任务组 * @return ResultMap */ @PostMapping(path = "/pauseJob") public BaseResult pauseJob(String jName, String jGroup) { try { quartzService.pauseJob(jName, jGroup); return CommonResult.buildSuccess("暂停任务成功"); } catch (SchedulerException e) { e.printStackTrace(); return CommonResult.buildFailure(500,"暂停任务失败"); } } /** * 恢复任务 * * @param jName 任务名称 * @param jGroup 任务组 * @return ResultMap */ @PostMapping(path = "/resumeJob") public BaseResult resumeJob(String jName, String jGroup) { try { quartzService.resumeJob(jName, jGroup); return CommonResult.buildSuccess("恢复任务成功"); } catch (SchedulerException e) { e.printStackTrace(); return CommonResult.buildFailure(500,"恢复任务失败"); } } /** * 重启任务 * * @param jName 任务名称 * @param jGroup 任务组 * @param cron cron表达式 * @return ResultMap */ @PostMapping(path = "/reScheduleJob") public BaseResult rescheduleJob(String jName, String jGroup, String cron) { try { quartzService.reScheduleJob(jName, jGroup, cron); return CommonResult.buildSuccess("重启任务成功"); } catch (SchedulerException e) { e.printStackTrace(); return CommonResult.buildFailure(500,"重启任务失败"); } } /** * 删除任务 * * @param jName 任务名称 * @param jGroup 任务组 * @return ResultMap */ @PostMapping(path = "/deleteJob") public BaseResult deleteJob(String jName, String jGroup) { try { quartzService.deleteJob(jName, jGroup); return CommonResult.buildSuccess("删除任务成功"); } catch (SchedulerException e) { e.printStackTrace(); return CommonResult.buildFailure(500,"删除任务失败"); } } }
service :
package com.hctrl.quartz.service; import com.hctrl.quartz.Job.HelloJob; import lombok.extern.slf4j.Slf4j; import org.quartz.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author hanchao * @version 1.0 * @date 2022/4/21 9:29 */ @Service @Slf4j public class QuartzService { @Autowired private Scheduler scheduler; /** * 新增定时任务 * * @param jName 任务名称 * @param jGroup 任务组 * @param tName 触发器名称 * @param tGroup 触发器组 * @param cron cron表达式 */ public void addJob(String jName, String jGroup, String tName, String tGroup, String cron) { try { // 构建JobDetail, 可以设置dataMap, 用于传递参数 JobDetail jobDetail = JobBuilder.newJob(HelloJob.class) .withIdentity(jName, jGroup) .build(); // 按新的cronExpression表达式构建一个新的trigger CronTrigger trigger = TriggerBuilder.newTrigger() .withIdentity(tName, tGroup) .startNow() .withSchedule(CronScheduleBuilder.cronSchedule(cron)) .build(); // 启动调度器 scheduler.start(); scheduler.scheduleJob(jobDetail, trigger); } catch (Exception e) { log.info("创建定时任务失败" + e); } } public void pauseJob(String jName, String jGroup) throws SchedulerException { scheduler.pauseJob(JobKey.jobKey(jName, jGroup)); } public void resumeJob(String jName, String jGroup) throws SchedulerException { scheduler.resumeJob(JobKey.jobKey(jName, jGroup)); } public void reScheduleJob(String jName, String jGroup, String cron) throws SchedulerException { TriggerKey triggerKey = TriggerKey.triggerKey(jName, jGroup); // 表达式调度构建器 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); // 按新的cronExpression表达式重新构建trigger trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); // 按新的trigger重新设置job执行,重启触发器 scheduler.rescheduleJob(triggerKey, trigger); } public void deleteJob(String jName, String jGroup) throws SchedulerException { scheduler.pauseTrigger(TriggerKey.triggerKey(jName, jGroup)); scheduler.unscheduleJob(TriggerKey.triggerKey(jName, jGroup)); scheduler.deleteJob(JobKey.jobKey(jName, jGroup)); } }
HelloJob : 执行任务类, 主要逻辑
package com.hctrl.quartz.Job; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.PersistJobDataAfterExecution; import java.time.LocalDateTime; /** * * @DisallowConcurrentExecution : 防止并发 * * @PersistJobDataAfterExecution : 默认每次执行都会创建一个新的HelloJob对象, 添加这个注解, 则变成单例 * * org.quartz.jobStore.misfireThreshold : 重要配置, 当一个任务到了时间执行, 但是上一个任务还没执行完成, * 并且添加了@DisallowConcurrentExecution注解, 则本次任务会延期执行, * 延期时间由这个配置决定, 如果配置了5s, 并且延期了5秒还是没有执行, 则本次任务不会执行 * * @author hanchao * @version 1.0 * @date 2022/4/21 10:22 */ @DisallowConcurrentExecution @PersistJobDataAfterExecution public class HelloJob implements Job { @Override public void execute(JobExecutionContext context) { String name = context.getJobDetail().getKey().getName(); String format = DateUtil.format(LocalDateTime.now(), DatePattern.NORM_DATETIME_PATTERN); System.out.println(name + " Hello Job执行时间: " + format); } }
结果图 :
-
Springboot整合Quartz
2020-04-21 15:33:481、Quartz核心概念 首先我们需要明白 Quartz 的几个核心概念,这样理解起 Quartz 的原理就会变得简单了。 Job表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:void execute(JobExecutionContext ...1、Quartz核心概念
首先我们需要明白 Quartz 的几个核心概念,这样理解起 Quartz 的原理就会变得简单了。
- Job 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:void execute(JobExecutionContext context)
- JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
- Trigger 代表一个调度参数的配置,什么时候去调。
- Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了
2、具体分析
2.1 Job其实由三部分组成:
- JobDetail: 用于描述这个Job是做什么的
- 一个实现Job接口的类:告诉Quartz具体干什么活的
- JobDataMap: 给 Job 提供参数用的
JobDetail jobDetail = newJob(TestJob.class) .withIdentity("job1","group2") .usingJobData("test","sparrow") .build();
可以看到我们在创建JobDetail的时候,将要执行的job的类名传给了JobDetail,所以scheduler就知道了要执行何种类型的job。每次当scheduler执行job时,在调用其execute(…)方法之前会创建该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收;
这种执行策略带来的一个后果是,job必须有一个无参的构造函数(当使用默认的JobFactory时);另一个后果是,在job类中,不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留,那这个时候JobDataMap就可以干活了,JobDataMap中可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据。
JobDataMap 除了像上面使用usingJobData 方式之外,还可以
jobDetail.getJobDataMap().put("test","testOne");
2.2 常用的两种Trigger
Trigger 就是触发器的意思,用来指定什么时间开始触发,触发多少次,每隔多久触发一次。最常用SimpleTrigger和CronTrigger这两种。
2.2.1、SimpleTrigger
Trigger trigger = newTrigger().withIdentity("myTrigger","group1") .startNow() .withSchedule(simpleSchedule() .withIntervalInSeconds(5) .withRepeatCount(10)) .build();
SimpleTrigger的属性包括:开始时间、结束时间、重复次数以及重复的间隔;上面这段代码的意思就是从现在开始,每隔5s执行一次,一共执行10次。
重复次数,可以是0、正整数,以及常量SimpleTrigger.REPEAT_INDEFINITELY。重复的间隔,必须是0,或者long型的正数,表示毫秒。注意,如果重复间隔为0,trigger将会以重复次数并发执行(或者以scheduler可以处理的近似并发数)。
定义开始时间,比如下方代码:5s后开始 对应的结束使用endAt
Date startTime = DateBuilder.futureDate(5, DateBuilder.IntervalUnit.SECOND); Trigger trigger1 = newTrigger().withIdentity("myTrigger","group1") .startAt(startTime) .build();
2.2.2、CronTrigger
使用CronTrigger最主要的就是Cron表达式,关于表达式后面会专门写一篇博文,这里直接放个例子了,每天上午10点42触发
trigger = newTrigger() .withIdentity("trigger3", "group1") .withSchedule(dailyAtHourAndMinute(10, 42)) .forJob("myJob", "group1") .build();
或者
trigger = newTrigger() .withIdentity("trigger3", "group1") .withSchedule(cronSchedule("0 42 10 * * ?")) .forJob("myJob", "group1") .build();
3、Springboot整合Quartz
以上只是简单地介绍了Quartz,好在整合过程中有个过度,更多内容可以查看Quartz相关资料
pom.xml
<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.0</version> </dependency>
业务代码
public class TestJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { JobDetail jobDetail = jobExecutionContext.getJobDetail(); String test = jobDetail.getJobDataMap().getString("test"); System.out.println(test+new Date()); } }
测试类:
@Test public void testThree() throws Exception{ //创建调度器 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); //定义一个触发器 Trigger trigger = newTrigger().withIdentity("myTrigger","group1") .startNow() .withSchedule(simpleSchedule() .withIntervalInSeconds(5) .withRepeatCount(5)) .build(); //定义一个JobDetail JobDetail jobDetail = newJob(TestJob.class) .withIdentity("job1","group2") .usingJobData("test","sparrow") .build(); //调度加入这个job scheduler.scheduleJob(jobDetail,trigger); //启动 scheduler.start(); //等待任务执行完再关闭 Thread.sleep(20000); scheduler.shutdown(true); }
执行结果:
扩展:实际应用
以上只是测试了一下是否可用,下面来个实际应用的例子:
先创建一个任务类,具体的业务逻辑根据实际情况来,我这里是系统给我发送一个邮件
public class DailyMailJob implements Job { Logger logger = LoggerFactory.getLogger(DailyMailJob.class); @Autowired IMailService mailService; @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { String content = "花有重开之日,人无再少年,走好脚下的路,笔直看眼前"; mailService.sendSimpleMail("XXXXXXXX@qq.com", Constant.DAILY_SUBJECT,content); logger.info("----------------邮件已发送----------------"); } }
再写个任务调度配置类 这里只是简单举个例子
@Configuration public class SchedulerConfig { @Autowired Scheduler scheduler; @Bean public void startJob() throws SchedulerException { customJobOne(scheduler); //启动 scheduler.start(); } private void customJobOne(Scheduler scheduler) throws SchedulerException { //创建调度器 //Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); //定义一个触发器 Trigger trigger = newTrigger().withIdentity("mailTriggerOne","mailGroup") .startNow() .withSchedule(cronSchedule("0 36 16 * * ?")) .build(); //定义一个JobDetail JobDetail jobDetail = newJob(DailyMailJob.class) .withIdentity("mailJobOne","JobGroup") .build(); //调度加入这个job scheduler.scheduleJob(jobDetail,trigger); } }
结果:
-
Springboot 整合 quartz
2021-12-22 15:36:051.下载quartz的sql文件 quartz下载链接 直接download,后解压找到 table_mysql.sql 数据库使用的是mysql数据库 2.将sql文件导入到需要链接的数据库,得到以下文件 3. 引入quartz的maven依赖 lombok我在代码... -
springboot与quartz框架整合
2019-04-15 15:30:15个人搭建的一个利用springboot和quartz框架整合的例子,可以进行一些任务的调度。 -
springBoot整合quartz持久化
2022-01-22 16:01:31springboot整合quartz实现分布式定时任务集群_sqlgao22的博客-CSDN博客_springboot 定时任务 字段描述 quartz中表及其表字段的意义_sqlgao22的博客-CSDN博客_qrtz_triggers表说明 相关表可以从官网下载 下载后解压 ... -
SpringBoot 整合 Quartz 实现分布式调度
2022-04-13 10:51:27springboot + quartz + postgres实现持久化分布式调度 集群环境任务调度测试 数据源使用spring默认数据源 -
SpringBoot整合Quartz实现定时任务
2022-02-21 17:20:58我们来介绍下springboot整合quartz定时器 1、导入依赖pom.xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> &... -
springboot整合quartz定时任务
2021-10-22 14:42:27springboot整合quartz,实现: 1.引入maven依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </... -
SpringBoot整合quartz2.2.3自定义数据源和websocket通信
2019-03-21 15:31:47SpringBoot整合quartz2.2.3使用自定义数据源,包括mysql和oracle,使用quartz配合websocket服务器向客户端主动通讯发起通讯。 -
【springBoot】springBoot整合Quartz和日志使用
2022-03-31 15:14:06文章目录一、SpringBoot整合Quartz1.1导入依赖1.2创建任务类1.3在启动类添加@EnableScheduling配置,然后直接启动测试二、SpringBoot日志2.1在resources目录下创建logback-spring.xml 一、SpringBoot整合Quartz 1.1... -
springboot2.0整合quartz
2018-12-27 17:14:04springboot2.0整合quartz,采用 spring-boot-starter-quartz方式,更为简单方式 -
springboot整合quartz定时任务( JDBC作业存储方式)
2022-03-12 19:52:23springboot整合quartz定时任务(任务持久化到数据库)