定时任务:Quartz 详解

article/2025/9/24 2:20:16

定时任务: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/


http://chatgpt.dhexx.cn/article/5SWKEhed.shtml

相关文章

Linux 环境下的 for循环嵌套学习

题目&#xff1a;输出下面的 4*5 的矩阵&#xff1a; 1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 解: 首先打开Xfce终端&#xff0c;创建c文件&#xff0c; 并用gedit记事本打开它。 touch zhanglong.c gedit zhanglong.c之后输入代码&#xff1a; #include<std…

for循环嵌套编程练习

1 编程要求:求出用50元,20元和10元换算100元有几种方式? 思路:用穷举法,将所有可能的情况都列举出来,用for循环可以实现穷举 分析:100元单用50换算,最多需要两张,用20元最多换算5张,用10元最多换算10张 #include<stdio.h> int main(void) {int w,e,s; // w代表…

c语言99乘法表循环嵌套写法,99乘法表(for循环嵌套)

计算乘法表 两个数相乘,外层循环代表被乘数,控制行数;内层代表乘数,控制列数。 循环嵌套,变量名不可以重复。 使用 break 语句让输出的乘法表更简洁。 使用 String 变量,做 String 的加法。 public class MultiTabble {public static void main(String[] args) {for (int…

关于For循环嵌套的简单理解

一&#xff1a;循环语句的嵌套 一个循环结构内可以含有另一个循环&#xff0c;称为循环嵌套&#xff0c;又称多重循环。常用的循环嵌套是二重循环&#xff0c;外层循环称为 外循环&#xff0c;内层循环称为内循环。 二&#xff1a;双重循环&#xff1a; 1.双重循环结构 for (循…

for循环嵌套的三种用法

目前本人了解到的常用的三种for循环嵌套&#xff0c;以下是对这三种循环嵌套的理解&#xff1a; 一&#xff1a;内外循环联动 var arr [[10, 20, 30],[hello, hi, world],[a, b, c]]for (var i 0; i < arr.length;i){let innerArr arr[i];for(var j 0;j < innerArr…

多重for循环嵌套

for循环定义&#xff1a; 同过一个简单的表达式&#xff0c;来完成多次重复性的代码功能&#xff1b;格式如下&#xff1a; for&#xff08;变量初始值&#xff1b;变量取值范围&#xff1b;变量自增/自减&#xff09;{ //代码块&#xff1b; } 一个复杂的for循环中可以嵌…

JS中的for循环嵌套

for 循环 for语句也是一种前测试循环语句&#xff0c;但它具有在 执行循环之前初始化变量 和 定义循环后要执行的代码的能力 for循环中专门提供了位置来放置循环的三个表达式 定义一个循环需要做实现这三步&#xff1a; 1、初始化表达式 2、条件表达式 3、更新表达式 通…

【Python】循环语句 ⑦ ( for 循环嵌套 | continue 临时跳过本次循环 | break 结束循环 )

文章目录 一、for 循环嵌套1、for 循环嵌套语法2、for 循环嵌套 - range 简单示例3、for 循环嵌套 - 打印乘法表示例 二、continue 临时跳过本次循环1、continue 关键字简介2、代码示例 - continue 简单用法3、代码示例 - continue 在嵌套循环中使用 三、break 结束循环1、brea…

js中for循环嵌套

首先我们的for循环单个就是我们将内容全部输出出来执行的条件 1.首先声明初始值 2.设置条件 3.执行代码块 4.执行i 代码如下&#xff0c; <button type"button" onclick"tests()">测试10</button><div id"dom10"></d…

for循环,for循环嵌套

for循环 for(var i 0;i<10;i) for循环用于遍历对象&#xff0c;并将对象中的数拿出来 for循环的括号里用两个分号把它分成了三个部分&#xff0c;第一部分是循环变量&#xff0c;第二部分是循环的判断条件&#xff0c;第三部分是变量的变化规律&#xff08;即每循环一次变…

Java for循环和Java for循环嵌套详解

for 语句是应用最广泛、功能最强的一种循环语句。大部分情况下&#xff0c;for 循环可以代替 while 循环、do while 循环。 for 语句是一种在程序执行前就要先判断条件表达式是否为真的循环语句。假如条件表达式的结果为假&#xff0c;那么它的循环语句根本不会执行。for 语句…

Java for循环嵌套for循环,你需要懂的代码性能优化技巧

前言 本篇分析的技巧点其实是比较常见的&#xff0c;但是最近的几次的代码评审还是发现有不少兄弟没注意到。 所以还是想拿出来说下。 正文 是个什么场景呢&#xff1f; 就是 for循环 里面还有 for循环&#xff0c; 然后做一些数据匹配、处理 这种场景。 我们结合实例代码来…

Java for循环嵌套

一、需求 需求1&#xff1a;打印以下图形 **** **** **** for(int i 0;i<3;i){//控制行数for(int j 0;j<4;j){//控制列数System.out.print("*");}System.out.println(); } 需求2&#xff1a;打印以下图形 …

C语言for循环语句及嵌套(误区,易错点要理解)

目录 前言 例题1&#xff1a; 例题2&#xff1a; for循环中contiune的使用&#xff1a; 前言 大家好&#xff0c;相信刚学C语言的小白们&#xff0c;觉得for循环是比较好用的&#xff0c;但是有时候写for循环语句的程序时&#xff0c;写出来的代码结果&#xff0c;和自己想要…

for循环嵌套

文章目录 一、什么是循环嵌套&#xff1f;二、实例1.输出一个矩阵2.输出九九乘法表 一、什么是循环嵌套&#xff1f; 外层循环和内层循环交叉执行&#xff0c;外层 for 每执行一次&#xff0c;内层 for 就要执行符合循环条件的次数。 二、实例 1.输出一个矩阵 代码如下&…

44. python的for循环嵌套

44. python的for循环嵌套 文章目录 44. python的for循环嵌套1. 什么是嵌套2. for循环中嵌套有if条件判断语句2.1 先创建一个奇数序列2.2 判断一个数是否能被7整除2.3 将2部分代码合二为一 3. for循环中嵌套有for循环 1. 什么是嵌套 嵌套是指一个对象中包含另一个与它相似的对象…

【C语言初学必看】一知半解的for循环嵌套for循环

&#x1f525;&#x1f680;前言目录&#xff1a; 初学C语言&#xff0c;常常遇到for循环中嵌套个for循环&#xff0c;初学者对于这种形式总是一知半解&#xff0c;这次我就整理了常见的for循环嵌套for循环的题目&#xff0c;我们一起争取一举拿下这类题。学废他们&#xff0c;…

双重for循环(嵌套for循环)

什么是双重循环&#xff1f; 双重循环&#xff1a;我们从字面上理解它&#xff0c;就是有两个循环套在一起 详细解释 双重for循环就是在一个for循环里在嵌套另一个for循环&#xff0c;即两个循环嵌套&#xff0c;还有多重循环嵌套的情况&#xff0c;但用的比较少&#xff0c…

DRN ---Closed-loop Matters: Dual Regression Networks for Single Image Super-Resolution

Closed-loop Matters: Dual Regression Networks for Single Image Super-Resolution 这篇博客主要介绍一下DRN&#xff0c;这是2020年最新出来超分辨重建文章。相信大家都阅读过很多超分辨率的文章&#xff0c;都知道超分辨率是一病态的问题。因为在现实生活中一张低分辨率图…

深度学习——Dual Regression Networks for Single Image Super-Resolution(DRN)

CVPR2020原论文&#xff1a;Closed-loop Matters: Dual Regression Networks forSingle Image Super-Resolution 开源代码&#xff08;pytorch框架&#xff09;&#xff1a;https://github.com/guoyongcs/DRN 1.问题 1&#xff09;低分辨率图像&#xff08;Low Resulotion&…