定时任务:Quartz 详解

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

    @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 管理。每个 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 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 的状态呢?答案就是:JobDataMapJobDetail 对象的一部分。

4.4 JobDataMap

JobDataMap 中可以包含不限量的(序列化的)数据对象,在 job 实例执行的时候,可以使用其中的数据;JobDataMapJava 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-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 {

    @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
  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 {
    @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 静态工具类
  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 {
    @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 默认的 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
     */
    @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();
    }
}

  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/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值