定时任务:Quartz 详解
文章目录
1 Quartz是什么?
Quartz 是 OpenSymphony 开源组织在 Job scheduling 领域又一个开源项目。
Quartz是一个完全由Java编写的开源作业调度框架,为在Java应用程序中进行作业调度提供了简单却强大的机制。Quartz可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz允许程序开发人员根据时间的间隔来调度作业。Quartz实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。
简而言之,Quartz 就是 Java 定时任务 领域一个非常优秀的框架。
2 Quartz核心组成
Job表示一个工作,即要执行的具体内容。此接口中只有一个方法,如下:
public interface Job {
void execute(JobExecutionContext var1) throws JobExecutionException;
}
JobDetail表示一个具体可执行的调度程序(Job的实现类),Job则是这个可执行调度程序执行的具体内容,另外JobDetail还包含了这个任务调度的方法和策略。Trigger触发器,指定运行参数。包括运行次数、运行开始时间和结束时间、运行时长等。Scheduler调度器,一个调度器中可以注册多个JobDetail和Trigger,当JobDetail和Trigger组合起来,就可以被Scheduler调度,此时定时任务被真正执行。
3 Quartz核心模块理解
3.1 用工厂模式理解 Quartz 的设计机制:
Job车间要生产的一类产品,例如汽车。Trigger一条生产线。一条生产线只能生产一个Job,但一个Job可由多条生产线同时生产。Scheduler车间总指挥,指挥调度车间内的生产任务(Scheduler内置线程池,线程池内的工作线程即为车间工人,每个工人承担着一组任务的真正执行)。
3.2 用流程图理解 Quartz 的核心模块关系:
下面流程图简略描述下JobDetail、Trigger 和 Scheduler 三者的关系:
- 一个
Trigger只能绑定一个JobDetail,但是一个JobDetail可由多个Trigger进行绑定(Trigger–>>JobDetail多对一)。 - 每个
JobDetail和Trigger通过group和name来唯一标识。 - 一个
Scheduler可以调度多组JobDetail和Trigger。

4 Quartz详解
4.1 Quartz的使用
4.1.1 Java类调度使用
4.1.1.1 导入依赖
<!--Quartz 定时任务-->
<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
4.1.1.2 新建Job类,重写execute方法
package com.ljr.utils;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.SchedulerException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class MyJob implements Job {
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
Object tv1 = jobExecutionContext.getTrigger().getJobDataMap().get("trigger1");
Object tv2 = jobExecutionContext.getTrigger().getJobDataMap().get("trigger2");
Object jv1 = jobExecutionContext.getJobDetail().getJobDataMap().get("jobDetail1");
Object jv2 = jobExecutionContext.getJobDetail().getJobDataMap().get("jobDetail2");
Object sv = null;
try {
sv = jobExecutionContext.getScheduler().getContext().get("scheduler");
} catch (SchedulerException e) {
e.printStackTrace();
}
System.out.println(sv);
System.out.println(tv1+":"+tv2);
System.out.println(jv1+":"+jv2);
System.out.println(Thread.currentThread().getName() + "--"
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
}
}
4.1.1.3 新建测试类,定时调用Job类
package com.ljr.utils;
import org.junit.Test;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.concurrent.TimeUnit;
public class MyJobTest {
@Test
public void jobTest() throws SchedulerException, InterruptedException {
//1、创建一个scheduler
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.getContext().put("scheduler", "生成scheduler!");
//2、创建JobDetail实例,并与MyJob类绑定(Job执行内容)
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.usingJobData("jobDetail1", "这是jobDetail第一个参数值!")
.withIdentity("myjob", "jobGroup")
.build();
//3、构建Trigger(触发器),定义执行频率和时长
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "triggerGroup")
.usingJobData("trigger1", "这是trigger第一个参数值!")
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3) //每隔3秒执行一次
.repeatForever() //永久执行
).build();
//4、往jobDetail的dataMap里存放键值对
jobDetail.getJobDataMap().put("jobDetail2", "这是jobDetail第二个参数值!");
//5、往trigger的dataMap里存放键值对
trigger.getJobDataMap().put("trigger2", "这是trigger第二个参数值!");
//6、组装jobDetail和trigger,交由scheduler调度
scheduler.scheduleJob(jobDetail, trigger);
//7、启动scheduler
scheduler.start();
//8、休眠,决定调度器运行时间,这里设置30s
TimeUnit.SECONDS.sleep(30);
//9、关闭scheduler
scheduler.shutdown();
}
}
4.1.1.3 执行结果
运行测试类,通过控制台输出内容可看出,每3秒执行一次定时任务,并于30秒之后结束定时任务。
生成scheduler!
这是trigger第一个参数值!:这是trigger第二个参数值!
这是jobDetail第一个参数值!:这是jobDetail第二个参数值!
DefaultQuartzScheduler_Worker-1--2022-05-16 21:44:01
生成scheduler!
这是trigger第一个参数值!:这是trigger第二个参数值!
这是jobDetail第一个参数值!:这是jobDetail第二个参数值!
DefaultQuartzScheduler_Worker-2--2022-05-16 21:44:04
生成scheduler!
这是trigger第一个参数值!:这是trigger第二个参数值!
这是jobDetail第一个参数值!:这是jobDetail第二个参数值!
DefaultQuartzScheduler_Worker-3--2022-05-16 21:44:07
生成scheduler!
这是trigger第一个参数值!:这是trigger第二个参数值!
这是jobDetail第一个参数值!:这是jobDetail第二个参数值!
DefaultQuartzScheduler_Worker-4--2022-05-16 21:44:10
生成scheduler!
这是trigger第一个参数值!:这是trigger第二个参数值!
这是jobDetail第一个参数值!:这是jobDetail第二个参数值!
DefaultQuartzScheduler_Worker-5--2022-05-16 21:44:13
生成scheduler!
这是trigger第一个参数值!:这是trigger第二个参数值!
这是jobDetail第一个参数值!:这是jobDetail第二个参数值!
DefaultQuartzScheduler_Worker-6--2022-05-16 21:44:16
生成scheduler!
这是trigger第一个参数值!:这是trigger第二个参数值!
这是jobDetail第一个参数值!:这是jobDetail第二个参数值!
DefaultQuartzScheduler_Worker-7--2022-05-16 21:44:19
生成scheduler!
这是trigger第一个参数值!:这是trigger第二个参数值!
这是jobDetail第一个参数值!:这是jobDetail第二个参数值!
DefaultQuartzScheduler_Worker-8--2022-05-16 21:44:22
生成scheduler!
这是trigger第一个参数值!:这是trigger第二个参数值!
这是jobDetail第一个参数值!:这是jobDetail第二个参数值!
DefaultQuartzScheduler_Worker-9--2022-05-16 21:44:25
生成scheduler!
这是trigger第一个参数值!:这是trigger第二个参数值!
这是jobDetail第一个参数值!:这是jobDetail第二个参数值!
DefaultQuartzScheduler_Worker-10--2022-05-16 21:44:28
生成scheduler!
这是trigger第一个参数值!:这是trigger第二个参数值!
这是jobDetail第一个参数值!:这是jobDetail第二个参数值!
DefaultQuartzScheduler_Worker-1--2022-05-16 21:44:31
Process finished with exit code 0
4.1.2 XML配置文件调度使用
4.1.2.1 导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
4.1.2.2 新建Job类,重写execute方法
package com.ljr.utils;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.SchedulerException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class MyJob implements Job {
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
Object tv1 = jobExecutionContext.getTrigger().getJobDataMap().get("trigger1");
Object tv2 = jobExecutionContext.getTrigger().getJobDataMap().get("trigger2");
Object jv1 = jobExecutionContext.getJobDetail().getJobDataMap().get("jobDetail1");
Object jv2 = jobExecutionContext.getJobDetail().getJobDataMap().get("jobDetail2");
Object sv = null;
try {
sv = jobExecutionContext.getScheduler().getContext().get("scheduler");
} catch (SchedulerException e) {
e.printStackTrace();
}
System.out.println(sv);
System.out.println(tv1+":"+tv2);
System.out.println(jv1+":"+jv2);
System.out.println(Thread.currentThread().getName() + "--"
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
}
}
4.1.2.3 Spring集成Quartz的配置文件
applicationContext.xml 文件中进行以下配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置Job类 -->
<bean id="myJob" class="com.ljr.utils.MyJob"></bean>
<!-- 配置JobDetail -->
<bean id="springQtzJobMethod" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<!-- 执行目标job -->
<property name="targetObject" ref="myJob"></property>
<!-- 要执行的方法 -->
<property name="targetMethod" value="execute"></property>
</bean>
<!-- 配置tirgger触发器 -->
<bean id="cronTriggerFactoryBean" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<!-- jobDetail -->
<property name="jobDetail" ref="springQtzJobMethod"></property>
<!-- cron表达式,执行时间 每5秒执行一次 -->
<property name="cronExpression" value="0/5 * * * * ?"></property>
</bean>
<!-- 配置调度工厂 -->
<bean id="springJobSchedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTriggerFactoryBean"></ref>
</list>
</property>
</bean>
</beans>
4.2 Job
一个 job 就是一个实现了 Job 接口的类,该接口只有一个方法:
Job 接口:
package org.quartz;
public interface Job {
void execute(JobExecutionContext var1) throws JobExecutionException;
}
我们创建具体的任务类时要继承 Job 并重写 execute() 方法,使用 JobBuilder 将具体任务类包装成一个 JobDetail(使用了建造者模式)交给 Scheduler 管理。每个 JobDetail 由 name 和 group 作为其唯一身份标识。
JobDataMap 中可以包含不限量的(序列化的)数据对象,在 job 实例执行的时候,可以使用其中的数据。
JobDataMap 继承 Map ,可通过键值对为 JobDetail 存储一些额外信息。
你定义了一个实现 Job 接口的类,这个类仅仅表明该 job 需要完成什么类型的任务,除此之外,Quartz 还需要知道该 Job 实例所包含的属性;这将由 JobDetail 类来完成。
4.3 JobDetail
JobDetail 实例是通过 JobBuilder 类创建的。
让我们先看看 Job 的特征(nature)以及 Job 实例的生命期。
// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1") // name "myJob", group "group1"
.build();
// Trigger the job to run now, and then every 40 seconds
Trigger trigger = newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);
现在考虑这样定义的作业类“HelloJob”:
public class HelloJob implements Job {
public HelloJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException
{
System.err.println("Hello! HelloJob is executing.");
}
}
可以看到,我们传给 scheduler 一个 JobDetail 实例,因为我们在创建 JobDetail 时,将要执行的 job 的类名传给了 JobDetail ,所以 scheduler 就知道了要执行何种类型的 job ;每次当 scheduler 执行 job 时,在调用其 execute(…) 方法之前会创建该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收;这种执行策略带来的一个后果是, job 必须有一个无参的构造函数(当使用默认的 JobFactory 时);另一个后果是,在 job 类中,不应该定义有状态的数据属性,因为在 job 的多次执行中,这些属性的值不会保留。
那么如何给 job 实例增加属性或配置呢?如何在 job 的多次执行中,跟踪 job 的状态呢?答案就是:JobDataMap,JobDetail 对象的一部分。
4.4 JobDataMap
JobDataMap 中可以包含不限量的(序列化的)数据对象,在 job 实例执行的时候,可以使用其中的数据;JobDataMap 是 Java Map 接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。
将 job 加入到 scheduler 之前,在构建 JobDetail 时,可以将数据放入 JobDataMap,如下示例:
// define the job and tie it to our DumbJob class
JobDetail job = newJob(DumbJob.class)
.withIdentity("myJob", "group1") // name "myJob", group "group1"
.usingJobData("jobSays", "Hello World!")
.usingJobData("myFloatValue", 3.141f)
.build();
在 job 的执行过程中,可以从 JobDataMap 中取出数据,如下示例:
public class DumbJob implements Job {
public DumbJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException
{
JobKey key = context.getJobDetail().getKey();
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
String jobSays = dataMap.getString("jobSays");
float myFloatValue = dataMap.getFloat("myFloatValue");
System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
}
}
如果你使用的是持久化的存储机制(后面会讲到),在决定 JobDataMap 中存放什么数据的时候需要小心,因为 JobDataMap 中存储的对象都会被序列化,因此很可能会导致类的版本不一致的问题;Java 的标准类型都很安全,如果你已经有了一个类的序列化后的实例,某个时候,别人修改了该类的定义,此时你需要确保对类的修改没有破坏兼容性。另外,你也可以配置 JDBC-JobStore 和 JobDataMap,使得 map中仅允许存储基本类型和 String 类型的数据,这样可以避免后续的序列化问题。
在 Job 执行时,JobExecutionContext 中的 JobDataMap 为我们提供了很多的便利。它是 JobDetail 中的 JobDataMap 和 Trigger 中的JobDataMap 的并集,但是如果存在相同的数据,则后者会覆盖前者的值。
4.5 Triggers
Trigger 最常用的两种类型:
-
SimpleTrigger:简单触发器,支持定义任务执行的间隔时间,执行次数的规则有两种,一是定义重复次数,二是定义开始时间和结束时间。如果同时设置了结束时间与重复次数,先结束的会覆盖后结束的,以先结束的为准。 -
CronTrigger:基于Cron表达式的触发器。
trigger 的公共属性有:
-
jobKey属性:当trigger触发时被执行的job的身份; -
startTime属性:设置trigger第一次触发的时间;该属性的值是java.util.Date类型,表示某个指定的时间点;有些类型的trigger,会在设置的startTime时立即触发,有些类型的trigger,表示其触发是在startTime之后开始生效。比如,现在是1月份,你设置了一个trigger–“在每个月的第5天执行”,然后你将startTime属性设置为4月1号,则该trigger第一次触发会是在几个月以后了(即4月5号)。 -
endTime属性:表示trigger失效的时间点。比如,”每月第5天执行”的trigger,如果其endTime是7月1号,则其最后一次执行时间是6月5号。
4.5.1 SimpleTrigger
SimpleTrigger 可以满足的调度需求是:在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次。比如,你有一个 trigger,你可以设置它在2015年1月13日的上午11:23:54准时触发,或者在这个时间点触发,并且每隔2秒触发一次,一共重复5次。
SimpleTrigger 的属性包括:开始时间、结束时间、重复次数以及重复的间隔。
下面的例子,是基于简单调度(simple schedule)创建的 trigger。
- 指定时间开始触发,不重复:
SimpleTrigger trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger1", "group1")
.startAt(myStartTime) // some Date
.forJob("job1", "group1") // identify job with name, group strings
.build();
- 指定时间触发,每隔10秒执行一次,重复10次:
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.startAt(myTimeToStartFiring) // if a start time is not given (if this line were omitted), "now" is implied
.withSchedule(simpleSchedule()
.withIntervalInSeconds(10)
.withRepeatCount(10)) // note that 10 repeats will give a total of 11 firings
.forJob(myJob) // identify job with handle to its JobDetail itself
.build();
- 5分钟以后开始触发,仅执行一次:
trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger5", "group1")
.startAt(futureDate(5, IntervalUnit.MINUTE)) // use DateBuilder to create a date in the future
.forJob(myJobKey) // identify job with its JobKey
.build();
- 立即触发,每个5分钟执行一次,直到22:00:
trigger = newTrigger()
.withIdentity("trigger7", "group1")
.withSchedule(simpleSchedule()
.withIntervalInMinutes(5)
.repeatForever())
.endAt(dateOf(22, 0, 0))
.build();
- 建立一个触发器,将在下一个小时的整点触发,然后每2小时重复一次:
trigger = newTrigger()
.withIdentity("trigger8") // because group is not specified, "trigger8" will be in the default group
.startAt(evenHourDate(null)) // get the next even-hour (minutes and seconds zero ("00:00"))
.withSchedule(simpleSchedule()
.withIntervalInHours(2)
.repeatForever())
// note that in this example, 'forJob(..)' is not called which is valid
// if the trigger is passed to the scheduler along with the job
.build();
scheduler.scheduleJob(trigger, job);
4.5.2 CronTrigger
CronTrigger 通常比 Simple Trigger 更有用,如果您需要基于日历的概念而不是按照 SimpleTrigger 的精确指定间隔进行重新启动的作业启动计划。
使用 CronTrigger,您可以指定号时间表,例如“每周五中午”或“每个工作日和上午9:30”,甚至“每周一至周五上午9:00至10点之间每5分钟”和1月份的星期五“。
即使如此,和 SimpleTrigger 一样,CronTrigger 有一个 startTime,它指定何时生效,以及一个(可选的) endTime,用于指定何时停止计划。
由7个子表达式组成字符串的,格式如下:
[秒] [分] [小时] [日] [月] [周] [年]
Cron Expressions示例
-
创建一个触发器的表达式,每5分钟就会触发一次。
“0 0/5 * * *?”
-
创建触发器的表达式,每5分钟触发一次,分钟后10秒(即上午10时10分,上午10:05:10等)。
“10 0/5 * * *?”
-
在每个星期三和星期五的10:30,11:30,12:30和13:30创建触发器的表达式。
“0 30 10-13?* WED,FRI“
-
创建触发器的表达式,每个月5日和20日上午8点至10点之间每半小时触发一次。请注意,触发器将不会在上午10点开始,仅在8:00,8:30,9:00和9:30。
“0 0/30 8-9 5,20 *?”
请注意,一些调度要求太复杂,无法用单一触发表示 - 例如“每上午9:00至10:00之间每5分钟,下午1:00至晚上10点之间每20分钟”一次。在这种情况下的解决方案是简单地创建两个触发器,并注册它们来运行相同的作业。
可通过在线生成Cron表达式的工具:http://cron.qqe2.com/ 来生成自己想要的表达式。

4.6 Scheduler
调度器,是 Quartz 的指挥官,由 StdSchedulerFactory 产生,它是单例的。Scheduler 中提供了 Quartz 中最重要的 API,默认是实现类是 StdScheduler。
Scheduler中主要的API大概分为三种:
- 操作
Scheduler本身:例如start、shutdown等; - 操作
Job,例如:addJob、pauseJob、pauseJobs、resumeJob、resumeJobs、getJobKeys、getJobDetail等; - 操作
Trigger,例如pauseTrigger、resumeTrigger等。
4.7 Quartz进阶使用
4.7.1 多触发器的定时任务
import com.quartz.demo.schedule.SimpleJob;
import org.junit.jupiter.api.Test;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.concurrent.TimeUnit;
public class MultiQuartzTest {
@Test
public void multiJobTest() throws SchedulerException, InterruptedException {
// 1、创建Scheduler(调度器)
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
// 2、创建JobDetail实例,与执行内容类SimpleJob绑定,注意要设置 .storeDurably(),否则报错
JobDetail jobDetail = JobBuilder.newJob(SimpleJob.class)
.withIdentity("job1", "job-group")
.storeDurably()
.build();
// 3、分别构建Trigger实例
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "trigger-group")
.startNow()//立即生效
.forJob(jobDetail)
.withSchedule(SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(2) //每隔3s执行一次
.repeatForever()) // 永久循环
.build();
Trigger trigger2 = TriggerBuilder.newTrigger().withIdentity("trigger2", "trigger-group")
.startNow()//立即生效
.forJob(jobDetail)
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3) //每隔5s执行一次
.repeatForever()).build(); // 永久循环
//4、调度器中添加job
scheduler.addJob(jobDetail, false);
scheduler.scheduleJob(trigger);
scheduler.scheduleJob(trigger2);
// 启动调度器
scheduler.start();
// 休眠任务执行时长
TimeUnit.SECONDS.sleep(20);
scheduler.shutdown();
}
}
4.7.2 Job中注入Bean
4.7.2.1 借助JobDataMap
- 在构建
JobDetail时,可以将数据放入JobDataMap,基本类型的数据通过usingJobData方法直接放入,mapper这种类型数据手动put进去:
@Autowired
private PersonMapper personMapper;
// 构建定时任务
JobDetail jobDetail = JobBuilder.newJob(MajorJob.class)
.withIdentity(jobName, jobGroupName)
.usingJobData("jobName", "QuartzDemo")
.build();
// 将mapper放入jobDetail的jobDataMap中
jobDetail.getJobDataMap().put("personMapper", personMapper);
- 在
job的执行过程中,可以从JobDataMap中取出数据,如下示例:
import com.quartz.demo.entity.Person;
import com.quartz.demo.mapper.PersonMapper;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
public class MajorJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) {
JobDataMap dataMap = jobExecutionContext.getJobDetail().getJobDataMap();
String jobName = dataMap.getString("jobName");
PersonMapper personMapper = (PersonMapper) dataMap.get("personMapper");
// 这样就可以执行mapper层方法了
List<Person> personList = personMapper.queryList();
System.out.println(Thread.currentThread().getName() + "--"
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()) + "--"
+ jobName + "--" + personList);
}
}
这个方案相对简单,但在持久化中会遇到 mapper 的序列化问题:
java.io.NotSerializableException: Unable to serialize JobDataMap for insertion into database because the value of property 'personMapper' is not serializable: org.mybatis.spring.SqlSessionTemplate
4.7.2.2 静态工具类
- 创建工具类
SpringContextJobUtil,实现ApplicationContextAware接口
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.Locale;
@Component
public class SpringContextJobUtil implements ApplicationContextAware {
private static ApplicationContext context;
@Override
@SuppressWarnings("static-access")
public void setApplicationContext(ApplicationContext contex) throws BeansException {
this.context = contex;
}
/**
* 根据name获取bean
*
* @param beanName name
* @return bean对象
*/
public static Object getBean(String beanName) {
return context.getBean(beanName);
}
public static String getMessage(String key) {
return context.getMessage(key, null, Locale.getDefault());
}
}
mapper类上打上@Service注解,并赋予其name:
@Service("personMapper")
public interface PersonMapper {
@Select("select id,name,age,sex,address,sect,skill,power,create_time createTime,modify_time modifyTime from mytest.persons")
List<Person> queryList();
}
Job中通过SpringContextJobUtil的getBean获取mapper的bean:
public class MajorJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) {
JobDataMap dataMap = jobExecutionContext.getJobDetail().getJobDataMap();
String jobName = dataMap.getString("jobName");
PersonMapper personMapper = (PersonMapper) SpringContextJobUtil.getBean("personMapper");
List<Person> personList = personMapper.queryList();
System.out.println(Thread.currentThread().getName() + "--"
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()) + "--"
+ jobName + "--" + personList);
}
}
4.8 Quartz持久化
定时任务的诸多要素,如任务名称、数量、状态、运行频率、运行时间等,是要存储起来的。JobStore ,就是用来存储任务和触发器相关的信息的。
Quartz 中有两种存储任务的方式,一种在在内存(RAMJobStore),一种是在数据库(JDBCJobStore)。
Quartz 默认的 JobStore 是 RAMJobstore,也就是把任务和触发器信息运行的信息存储在内存中,用到了 HashMap、TreeSet、HashSet 等等数据结构,如果程序崩溃或重启,所有存储在内存中的数据都会丢失。所以我们需要把这些数据持久化到磁盘。
实现Quartz的持久化并不困难,按下列步骤操作即可:
- 添加相关依赖:
<!--Quartz 使用的连接池 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
- 编写配置:
import org.quartz.Scheduler;
import org.quartz.ee.servlet.QuartzInitializerListener;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
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 java.io.IOException;
import java.util.Properties;
/**
* @author muguozheng
* @version 1.0.0
* @createTime 2022/4/19 18:46
* @description Quartz配置
*/
@Configuration
public class SchedulerConfig {
/**
* 读取quartz.properties,将值初始化
*
* @return Properties
* @throws IOException io
*/
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
/**
* 将配置文件的数据加载到SchedulerFactoryBean中
*
* @return SchedulerFactoryBean
* @throws IOException io
*/
@Bean
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setQuartzProperties(quartzProperties());
return schedulerFactoryBean;
}
/**
* 初始化监听器
*
* @return QuartzInitializerListener
*/
@Bean
public QuartzInitializerListener executorListener() {
return new QuartzInitializerListener();
}
/**
* 获得Scheduler对象
*
* @return Scheduler
* @throws IOException io
*/
@Bean
public Scheduler scheduler() throws IOException {
return schedulerFactoryBean().getScheduler();
}
}
- 创建
quartz.properties配置文件
# 实例化ThreadPool时,使用的线程类为SimpleThreadPool
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
# 并发个数
org.quartz.threadPool.threadCount=10
# 优先级
org.quartz.threadPool.threadPriority=3
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
org.quartz.jobStore.misfireThreshold=5000
# 持久化使用的类
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
# 数据库中表的前缀
org.quartz.jobStore.tablePrefix=QRTZ_
# 数据源命名
org.quartz.jobStore.dataSource=qzDS
# qzDS 数据源
org.quartz.dataSource.qzDS.driver=com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL=jdbc:mysql://localhost:3306/mytest?useUnicode=true&characterEncoding=UTF-8
org.quartz.dataSource.qzDS.user=root
org.quartz.dataSource.qzDS.password=root
org.quartz.dataSource.qzDS.maxConnections=10
- 创建
Quartz持久化数据的表:数据表初始化 sql 放置在External Libraries的org/quartz/impl/jdbcjobstore中,直接用其初始化相关表即可。要注意的是,用来放置这些表的库要与quartz.properties的库一致。
参考文献:



2154

被折叠的 条评论
为什么被折叠?



