定时任务:Quartz 详解


文章目录

  • 定时任务:Quartz 详解
  • 1 Quartz是什么?
  • 2 Quartz核心组成
  • 3 Quartz核心模块理解
    • 3.1 用工厂模式理解 Quartz 的设计机制:
    • 3.2 用流程图理解 Quartz 的核心模块关系:
  • 4 Quartz详解
    • 4.1 Quartz的使用
      • 4.1.1 Java类调度使用
        • 4.1.1.1 导入依赖
        • 4.1.1.2 新建Job类,重写execute方法
        • 4.1.1.3 新建测试类,定时调用Job类
        • 4.1.1.3 执行结果
      • 4.1.2 XML配置文件调度使用
        • 4.1.2.1 导入依赖
        • 4.1.2.2 新建Job类,重写execute方法
        • 4.1.2.3 Spring集成Quartz的配置文件
    • 4.2 Job
    • 4.3 JobDetail
    • 4.4 JobDataMap
    • 4.5 Triggers
      • 4.5.1 SimpleTrigger
      • 4.5.2 CronTrigger
    • 4.6 Scheduler
    • 4.7 Quartz进阶使用
      • 4.7.1 多触发器的定时任务
      • 4.7.2 Job中注入Bean
        • 4.7.2.1 借助JobDataMap
        • 4.7.2.2 静态工具类
    • 4.8 Quartz持久化

1 Quartz是什么?

QuartzOpenSymphony 开源组织在 Job scheduling 领域又一个开源项目。

  • Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。
  • Quartz 可以与 J2EEJ2SE 应用程序相结合也可以单独使用。
  • Quartz 允许程序开发人员根据时间的间隔来调度作业。
  • Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。

简而言之,Quartz 就是 Java 定时任务 领域一个非常优秀的框架。

2 Quartz核心组成

  • Job 表示一个工作,即要执行的具体内容。此接口中只有一个方法,如下:
public interface Job {void execute(JobExecutionContext var1) throws JobExecutionException;
}
  • JobDetail 表示一个具体可执行的调度程序(Job的实现类),Job 则是这个可执行调度程序执行的具体内容,另外 JobDetail 还包含了这个任务调度的方法和策略。
  • Trigger 触发器,指定运行参数。包括运行次数、运行开始时间和结束时间、运行时长等。
  • Scheduler 调度器,一个调度器中可以注册多个 JobDetailTrigger ,当 JobDetailTrigger 组合起来,就可以被 Scheduler 调度,此时定时任务被真正执行。

3 Quartz核心模块理解

3.1 用工厂模式理解 Quartz 的设计机制:

  • Job 车间要生产的一类产品,例如汽车。
  • Trigger 一条生产线。一条生产线只能生产一个 Job ,但一个 Job 可由多条生产线同时生产。
  • Scheduler 车间总指挥,指挥调度车间内的生产任务( Scheduler 内置线程池,线程池内的工作线程即为车间工人,每个工人承担着一组任务的真正执行)。

3.2 用流程图理解 Quartz 的核心模块关系:

下面流程图简略描述下JobDetailTriggerScheduler 三者的关系:

  • 一个 Trigger 只能绑定一个 JobDetail ,但是一个 JobDetail 可由多个 Trigger 进行绑定(Trigger–>>JobDetail 多对一)。
  • 每个 JobDetailTrigger 通过 groupname 来唯一标识。
  • 一个 Scheduler 可以调度多组 JobDetailTrigger

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 {@Testpublic void jobTest() throws SchedulerException, InterruptedException {//1、创建一个schedulerScheduler 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、启动schedulerscheduler.start();//8、休眠,决定调度器运行时间,这里设置30sTimeUnit.SECONDS.sleep(30);//9、关闭schedulerscheduler.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:31Process 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/beanshttp://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 管理。每个 JobDetailnamegroup 作为其唯一身份标识。

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 classJobDetail job = newJob(HelloJob.class).withIdentity("myJob", "group1") // name "myJob", group "group1".build();// Trigger the job to run now, and then every 40 secondsTrigger trigger = newTrigger().withIdentity("myTrigger", "group1").startNow().withSchedule(simpleSchedule().withIntervalInSeconds(40).repeatForever())            .build();// Tell quartz to schedule the job using our triggersched.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 的状态呢?答案就是:JobDataMapJobDetail 对象的一部分。

4.4 JobDataMap

JobDataMap 中可以包含不限量的(序列化的)数据对象,在 job 实例执行的时候,可以使用其中的数据;JobDataMapJava Map 接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。

job 加入到 scheduler 之前,在构建 JobDetail 时,可以将数据放入 JobDataMap,如下示例:

  // define the job and tie it to our DumbJob classJobDetail 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-JobStoreJobDataMap,使得 map中仅允许存储基本类型和 String 类型的数据,这样可以避免后续的序列化问题。

Job 执行时,JobExecutionContext 中的 JobDataMap 为我们提供了很多的便利。它是 JobDetail 中的 JobDataMapTrigger 中的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

  1. 指定时间开始触发,不重复:
    SimpleTrigger trigger = (SimpleTrigger) newTrigger() .withIdentity("trigger1", "group1").startAt(myStartTime)                     // some Date .forJob("job1", "group1")                 // identify job with name, group strings.build();
  1. 指定时间触发,每隔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();
  1. 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();
  1. 立即触发,每个5分钟执行一次,直到22:00:
    trigger = newTrigger().withIdentity("trigger7", "group1").withSchedule(simpleSchedule().withIntervalInMinutes(5).repeatForever()).endAt(dateOf(22, 0, 0)).build();
  1. 建立一个触发器,将在下一个小时的整点触发,然后每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示例

  1. 创建一个触发器的表达式,每5分钟就会触发一次。

    “0 0/5 * * *?”

  2. 创建触发器的表达式,每5分钟触发一次,分钟后10秒(即上午10时10分,上午10:05:10等)。

    “10 0/5 * * *?”

  3. 在每个星期三和星期五的10:30,11:30,12:30和13:30创建触发器的表达式。

    “0 30 10-13?* WED,FRI“

  4. 创建触发器的表达式,每个月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 {@Testpublic 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、调度器中添加jobscheduler.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

  1. 在构建 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);
  1. 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 {@Overridepublic 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 静态工具类

  1. 创建工具类 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());}
}
  1. 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();
}
  1. Job 中通过 SpringContextJobUtilgetBean 获取 mapper 的bean:
public class MajorJob implements Job {@Overridepublic 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 默认的 JobStoreRAMJobstore,也就是把任务和触发器信息运行的信息存储在内存中,用到了 HashMapTreeSetHashSet 等等数据结构,如果程序崩溃或重启,所有存储在内存中的数据都会丢失。所以我们需要把这些数据持久化到磁盘。

实现Quartz的持久化并不困难,按下列步骤操作即可:

  1. 添加相关依赖:
<!--Quartz 使用的连接池 -->
<dependency><groupId>com.mchange</groupId><artifactId>c3p0</artifactId><version>0.9.5.2</version>
</dependency>
  1. 编写配置:
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*/@Beanpublic 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*/@Beanpublic SchedulerFactoryBean schedulerFactoryBean() throws IOException {SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();schedulerFactoryBean.setQuartzProperties(quartzProperties());return schedulerFactoryBean;}/*** 初始化监听器** @return QuartzInitializerListener*/@Beanpublic QuartzInitializerListener executorListener() {return new QuartzInitializerListener();}/*** 获得Scheduler对象** @return Scheduler* @throws IOException io*/@Beanpublic Scheduler scheduler() throws IOException {return schedulerFactoryBean().getScheduler();}
}
  1. 创建 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
  1. 创建 Quartz 持久化数据的表:数据表初始化 sql 放置在 External Librariesorg/quartz/impl/jdbcjobstore 中,直接用其初始化相关表即可。要注意的是,用来放置这些表的库要与 quartz.properties 的库一致。

参考文献:

https://blog.csdn.net/mu_wind/article/details/124257719

https://www.w3cschool.cn/quartz_doc/

定时任务:Quartz 详解相关推荐

  1. Linux crontab 定时任务命令详解

    Linux Crontab 定时任务 命令详解 [日期:2016-02-13] 来源:Linux社区 在工作中需要数据库在每天零点自动备份所以需要建立一个定时任务.我选择在Linux下使用Cronta ...

  2. linux cron 服务,Linux定时任务Crontab详解(推荐)

    今天做了个数据库的备份脚本,顺便系统得学习一下linux下定时执行脚本的设置.Linux下的定时执行主要是使用crontab文件中加入定制计划来执行,设置比Windows稍微复杂一些(因为没有图形界面 ...

  3. spring框架使用Quartz执行定时任务实例详解

    版权声明:本文为博主原创文章,如需转载,请标明出处. https://blog.csdn.net/alan_liuyue/article/details/80382324 Quartz简介 1.Qua ...

  4. Java定时任务工具详解之Timer篇

    Java定时任务调度工具详解 什么是定时任务调度? ◆ 基于给定的时间点,给定的时间间隔或者给定的执行次数自动执行的任务. 在Java中的定时调度工具? ◆ Timer ◆Quartz Timer和Q ...

  5. Quartz详解与使用

    目录 1.Quartz介绍 2. Quartz的触发器 2.1五种类型的Trigger(定时器) 3.储存方式 3.2 cron表达式 4.Quartz创建定时任务 4.1导入Quartz依赖 4.2 ...

  6. Quartz详解和使用CommandLineRunner在项目启动时初始化定时任务

    文章目录 Quartz介绍 自定义CommandLineRunner类: 创建.更新定时任务 service层 自定义QuartzJobBean 智能调度组件 定时任务实体类: mapper接口: 时 ...

  7. Quartz学习总结(2)——定时任务框架Quartz详解

    一.概述 Quartz是OpenSymphony开源组织的一个开源项目,定时任务框架,纯Java语言实现,最新版本为2.3.0. Quartz中用到的设计模式:Builder模式.Factory模式. ...

  8. quartz 每月一次_Quartz 定时任务框架详解

    1.Quartz 体系结构 Quartz 设计有四个核心类,分别是Scheduler(调度器).Job(任务) .Trigger(触发器).JobDetail(任务详情),他们是使用Quartz的关键 ...

  9. java定时任务工具详解之Quartz

    Quartz简介 特点: 强大的调度功能 灵活的应用方式 分布式和集群能力 主要用到的设计模式 Builder模式 Factory模式 组件模式 链式写法 三个核心概念 调度器 任务 触发器 第一个Q ...

最新文章

  1. 张涵20160401作业
  2. c语言map函数k v都是int,Go语言sync.Map(在并发环境中使用的map)
  3. 如何快速压测电商网站?
  4. Junit_测试概述
  5. 题解 P5301 【[GXOI/GZOI2019]宝牌一大堆】
  6. event.srcElement 与event.target
  7. php entity,PHP html_entity_decode() 函数_程序员人生
  8. java学习之路 之 Java集合练习题
  9. 汇佳学校|应博丞:不及格“逆袭”全科满分,粉丝科普博主唤醒想象力
  10. 目前域名防封的新知识
  11. AI语音红外遥控配网教程
  12. 大学计算机专业课程体系
  13. 计算机应用基础 红头文件,计算机基础教学的计划.pdf
  14. 数据库范式1NF 2NF 3NF详细阐述
  15. 恢复误删excel工作薄中的表格
  16. 【元宵快乐】猜灯谜吃元宵 元气满满闹元宵~(附猜灯谜小游戏)
  17. VC学习资料收集(12):VC小知识总结
  18. 【微信支付】APP支付和APIJS支付
  19. Java数据结构之栈与计算器
  20. 抖音巨量引擎1 创建广告计划

热门文章

  1. [寒江孤叶丶的Cocos2d-x之旅_30]Cocos2d-x 3.5 EditBox无法显示字体的原因
  2. 在哪些情况下你会毫不犹豫的选择辞职?
  3. 体验有奖 | 3步玩转云原生数仓AnalyticDB MySQL电商行业数据分析场景
  4. jira 查找issue_MySQL:更新JIRA里issue的创建和更新时间 | 学步园
  5. 反射与工厂模式:早餐店的发展之路
  6. RunZeBlog项目开发流程
  7. 小站长如何利用软文进行推广网站
  8. 微博同城热搜抓取逻辑(333城市)
  9. 2020山东省计算机专科学校排名,2019-2020山东专科学校排名及分数线(理科+文科)...
  10. AutoCAD二维平面图,线绘——茶道,意境图