使用Quartz的4种方式

quartz的介绍与4种用法
一,quartz的介绍
程序开发中,经常能够遇到开发定时任务的情况,java中常见的定时任务的开发方式一共有3种,第一种是java的util中的Timer类的方法,这种方法的API比较简单,但是所有的定时任务运行都是在内存中,扩展性和性能和功能性都不好; 第2中是使用springTask ,使用这种方式的好处是在Spring的项目中创建和配置定时任务比较简单,但是也有瓶颈,那就是定时任务的信息都保存在内存中,一旦定时任务太多,或者服务器重启,定时任务的配置和已经运行的结果没法保存,而且不能扩展定时任务服务器成分布式服务器.
quartz是专门为java环境设计的定时任务框架,功能强大,不经可以支持各种形式的定时任务,而且定时任务序列化方案也支持序列化在内存中或者保存在数据库中,而且可以扩展成分布式的定时任务服务器,是一种全能的重型定时任务服务框架

二.Quartz的主要对象的介绍
(1)核心类
QuartzSchedulerThread:负责执行向QuartzScheduler注册的触发Trigger的工作的线程。
ThreadPool:Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提供运行效率。
QuartzSchedulerResources:包含创建QuartzScheduler实例所需的所有资源(JobStore,ThreadPool等)。
SchedulerFactory :提供用于获取调度程序实例的客户端可用句柄的机制。
JobStore: 通过类实现的接口,这些类要为org.quartz.core.QuartzScheduler的使用提供一个org.quartz.Job和org.quartz.Trigger存储机制。作业和触发器的存储应该以其名称和组的组合为唯一性。
QuartzScheduler :这是Quartz的核心,它是org.quartz.Scheduler接口的间接实现,包含调度org.quartz.Jobs,注册org.quartz.JobListener实例等的方法。
Scheduler :这是Quartz Scheduler的主要接口,代表一个独立运行容器。调度程序维护JobDetails和触发器的注册表。 一旦注册,调度程序负责执行作业,当他们的相关联的触发器触发(当他们的预定时间到达时)。
Trigger :具有所有触发器通用属性的基本接口,描述了job执行的时间出发规则。 - 使用TriggerBuilder实例化实际触发器。
JobDetail :传递给定作业实例的详细信息属性。 JobDetails将使用JobBuilder创建/定义。
Job:要由表示要执行的“作业”的类实现的接口。只有一个方法 void execute(jobExecutionContext context)
(jobExecutionContext 提供调度上下文各种信息,运行时数据保存在jobDataMap中)

Job有个子接口StatefulJob ,代表有状态任务。有状态任务不可并发,前次任务没有执行完,后面任务处于阻塞等到。

(2)核心类之间的关系的图解
使用Quartz的4种方式

Quartz 中五种类型的 Trigger:SimpleTrigger,CronTirgger,DateIntervalTrigger,NthIncludedDayTrigger和Calendar 类( org.quartz.Calendar)。
最常用的:
SimpleTrigger:用来触发只需执行一次或者在给定时间触发并且重复N次且每次执行延迟一定时间的任务。
CronTrigger:按照日历触发,例如“每个周五”,每个月10日中午或者10:15分。

三,Quartz的序列化方式
Quartz 可以根据选择2种序列化定时任务信息的方式RAMJobStore和JDBCJobStore
类型 优点 缺点
RAMJobStore 不要外部数据库,配置容易,运行速度快 因为调度程序信息是存储在被分配给JVM的内存里面,所以,当应用程序停止运行时,所有调度信息将被丢失。另外因为存储到JVM内存里面,所以可以存储多少个Job和Trigger将会受到限制
JDBCJobStore 支持集群,因为所有的任务信息都会保存到数据库中,可以控制事物,还有就是如果应用服务器关闭或者重启,任务信息都不会丢失,并且可以恢复因服务器关闭或者重启而导致执行失败的任务 运行速度的快慢取决与连接数据库的快慢

四,使用Quartz的4种方式
在使用Quarts之前,先要导入以下的依赖

<?xml version="1.0" encoding="UTF-8"?>


4.0.0

<groupId>com.fufulong</groupId>
<artifactId>spring-quarz-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
    <!-- spring版本号 -->
    <spring.version>4.2.2.RELEASE</spring.version>
    <!-- Quartz的版本 -->
    <quarz.version>2.2.2</quarz.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-oxm</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.2.2</version>
    </dependency>
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz-jobs</artifactId>
        <version>2.2.2</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.37</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>com.mchange</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.5.2</version>
    </dependency>
    <!--&lt;!&ndash; druid数据库连接池 &ndash;&gt;-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.8</version>
    </dependency>
    <!--测试相关的2个包-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <!-- 表示开发的时候引入,发布的时候不会加载此包 -->
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency> 
    <!-- mybatis核心包 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.2.6</version>
    </dependency>
    <!-- mybatis/spring包 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>1.2.2</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>5.4.1.Final</version>
    </dependency>
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper</artifactId>
        <version>4.0.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/cglib/cglib -->
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.1</version>
    </dependency>


</dependencies>

<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
        </resource>
    </resources>
    <plugins>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>utf8</encoding>
            </configuration>
        </plugin>
    </plugins>
</build>
  1. 第1种: Job不基于特定的定时QuartzBean,使用MethodInvokingJobDetailFactoryBean
    使用者种方式配置定时任务主要有下面几部
  1. . 先定义一个任意的类和一个任意的public 权限的方法记住这个Job类的全类名和方法名
    2). 在Spring 的配置文件中配置这个Job类的Bean
    3). 使用 MethodinvokingJobDetailFactoryBean配置一个Bean,并引用配置Job,并指定工作方法名
    4). 配置触发器,触发器可能是SimpleTrIgger挥着cronTrigger
    5).配置quartz的任务调度器SchedulerFactoryBean,并表示出要启动的触发器.还可以在这里配置一些调度器先关的配置,比如是否随着spring容器启动立刻启动等
    具体的配置文件如下面的配置文件所示 applicationContext1.xml
<?xml version="1.0" encoding="UTF-8"?>

<!-- 1.先配置SimpleJob相关 Bean-->
<bean id="simpleJob" class="com.fufulong.xmlType.SimpleJob"></bean>
<bean id="simpleJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="simpleJob"/>
    <property name="targetMethod" value="printDate"/>
    <!--不允许并发-->
    <property name="concurrent" value ="false"/>
</bean>


<bean id="simpleTriggerFactoryBean" class ="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
    <!--任务详情-->
    <property name="jobDetail" ref="simpleJobDetail"/>
      <!-- 第一次执行在1秒后,单位毫秒-->
    <property name="startDelay" value="1000"/>
    <!-- 每次执行之间的间隔时间 2秒,单位:毫秒-->
    <property name="repeatInterval" value="2000"/>
    <!--总共执行几次-->
    <property name="repeatCount" value="9"/>
    <!-- 执行优先级-->
    <property name="priority" value="1"/>
    <property name="description" value="simpleJob"/>
</bean>

<!-- 2.配置 cron任务的触发器和JobDetail-->
<bean id="cronJob" class="com.fufulong.xmlType.CronJob"/>
<bean id ="cronJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="cronJob"/>
    <property name="targetMethod" value="printDate"/>

</bean>
<bean id ="cronTriger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="jobDetail" ref="cronJobDetail"/>
    <property name="cronExpression" value="0/3 * * * * ?"/>
</bean>



<!-- 配置 任务调度器,把需要启动的触发器放进去-->
<bean id="springJobSchedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="simpleTriggerFactoryBean"/>
            <ref bean="cronTriger"/>
        </list>
    </property>
</bean>

下面是上面2个Job类的位置截图和代码详情
使用Quartz的4种方式

package com.fufulong.xmlType;

import java.text.SimpleDateFormat;
import java.util.Date;

/**

  • 使用简单单一循环表达是的定时任务 JobDetail表是类
    /
    public class SimpleJob {
    /
    *
    • 任务方法,打印现在的时间
      */
      public void printDate(){
      SimpleDateFormat format = new SimpleDateFormat(“yyyy-mm-dd HH:mm:ss”);
      System.out.println(“simpleJob --” + format.format(new Date()));
      }
      }

package com.fufulong.xmlType;

import java.text.SimpleDateFormat;
import java.util.Date;

/**

  • 使用Cron表达是作为触发器的任务类
    /
    public class CronJob {
    /
    *
    • 定时任务方法,也是答应当前时间
      */
      public void printDate(){
      SimpleDateFormat format = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
      System.out.println(“cronJob----” + format.format(new Date()));
      }
      }

测试类如下所示,这种方式对应的是 testXmlTypeQuarz()
package test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class QuarzTest {

public static void main(String[] args) {

// testAnnotationTypeQuarz();
// testXmlTypeQuarz();
testQuarzBean();
}

public static void testXmlTypeQuarz(){
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext1.xml");

    System.out.println("测试完毕");
}

public static void testAnnotationTypeQuarz(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext2.xml");
    System.out.println("测试完成..");
}

public static  void testQuarzBean(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext3.xml");
    System.out.println("测试完成..");
}

}

  1. 基于Spring的Bean扫描和@Scheduled注解来配置定时任务
    这种方式的优点是: 配置简单,不需要再xmL文件或者java配置类中为每一个定时任务都配置一套 job,jobdetail,trigger和 ScheduleFactoryBean,缺点是没办法序列化定时任务信息,而且实际上使用的是Spring的定时任务框架 SpringTask.
    值得注意的是,注解扫描配置的定时任务 是能够和从xml配置的或者java配置类中配置的定时任务同时生效的.
    这种方式配置定时任务的步骤是:
    1), 创建一个任务包,专门放各种定时任务
    2),Spring的主配置文件中采用task:annotation-driven/
    <context:component-scan base-package=“com.fufulong.annotationType”/>
    的方式配置任务扫描和 component-scan

下面就是先关的包的位置和定时任务的类的详细代码

package com.fufulong.annotationType;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

/**

  • 采用注解扫描的任务类
    /
    @Component
    public class AnnotationJob {
    /
    *

    • 1.必须在 component中,存在一个方法,方法的返回值类型是void,然后不能有参数
    • 2.必须有注解 @Schedule
    • fixedDelay控制方法执行的间隔时间,是以上一次方法执行完开始算起,如上一次方法执行阻塞住了,
    • 那么直到上一次执行完,并间隔给定的时间后,执行下一次。
    • fixedRate是按照一定的速率执行,是从上一次方法执行开始的时间算起,如果上一次方法阻塞住了,
    • 下一次也是不会执行,但是在阻塞这段时间内累计应该执行的次数,当不再阻塞时,一下子把这些全部执行掉,
    • 而后再按照固定速率继续执行。
    • initialDelay 。如: @Scheduled(initialDelay = 10000,fixedRate = 15000
    • 这个定时器就是在上一个的基础上加了一个initialDelay = 10000 意思就是在容器启动后,延迟10秒后再执行一次定时器,
    • 以后每15秒再执行一次该定时器。

    */
    @Scheduled(fixedRate = 1000,initialDelay = 3000)
    public void task1(){
    SimpleDateFormat format = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
    String nowStr = format.format(new Date());
    System.out.println(“注解驱动的SimpleJob1,打印日期,fixedRate = 1000”);
    System.out.println(nowStr);
    }

    @Scheduled(cron = “0/5 * * * * ?”)
    public void task2(){
    SimpleDateFormat format = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
    String nowStr = format.format(new Date());
    System.out.println("注解驱动的CronJob1,打印日期 cron = ‘0/5 * * * * ?’ ");
    System.out.println(nowStr);
    }

}

  1. 第3种,基于特定的Quartz Bean
    这种方式好处有2点;
    第1,可以在配置 JobBean 的时候,配合Job类的属性,为job的逻辑加上自定义的动态的参数,从而可以实现一些需要动态参数的定时任务
    第2, 实现的定时任务的方法里面有 JobContext 对象,可以从这个对象中得到定时任务的很多信息

这种方式配置定时任务主要有以下几个步骤:
1).创建一个自己的类继承org.springframework.scheduling.quartz.QuartzJobBean,重写里面的抽象方法:protected void executeInternal(JobExecutionContext jobExecutionContext)
2). 如果这个定时任务需要动态制定自定义的参数,可以在这个我们自定义的类中加上相应类型的参数,并加上set 和 get方法.注意set方法一定要是public修饰的
3),在重写的 executeInternal方法中可以从 JobExecutionContext对象中得到定时任务的很多相关信息,包括 jobdetail,trigger,ScheduleFactory的很多信息.
4), 配置JobDetail,trigger,线程池,ScheduleFactory等.
配置文件如下: applicationContext3.xml

<?xml version="1.0" encoding="UTF-8"?>

<bean id ="jobdatail1" class = "org.springframework.scheduling.quartz.JobDetailFactoryBean">
    <property name="description" value="测试基于特定工作类的quarz的用法"/>
    <property name="name" value="joubdatail1"/>
    <property name="group" value="第1组"/>
    <property name="jobClass" value="com.fufulong.quarzbean.QuarzbeanJob"/>
    <property name="jobDataAsMap">
        <map>
            <entry key="timeout" value="0"/>
            <entry key ="age" value = "22"/>
        </map>
    </property>

</bean>

<bean id ="simpleTrigger" class = "org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
    <property name="jobDetail" ref="jobdatail1"/>
    <property name="repeatCount" value="10"/>
    <property name="repeatInterval" value ="4000"/>
    <property name="startDelay" value="0"/>

</bean>
 <!--调度器-->
<bean id ="schedule" class ="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers" >
        <list>
            <ref bean="simpleTrigger"/>
        </list>
    </property>
    <property name="taskExecutor" ref ="executor"/>
    <property name="autoStartup" value="true"/>
    <property name="startupDelay" value="5"/>
    <property name="jobFactory" ref = "jobFactory"/>

</bean>

<!--定时任务调度器 Schedule需要用到的线程池配置-->
<bean id="executor"
      class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <!-- 核心线程数 线程池维护线程的最少数量 -->
    <property name="corePoolSize" value="100" />
    <!-- 线程池维护线程所允许的空闲时间 -->
    <property name="keepAliveSeconds" value="200" />
    <!-- 线程池维护线程的最大数量 -->
    <property name="maxPoolSize" value="100" />
    <!-- 线程池所使用的缓冲队列 -->
    <property name="queueCapacity" value="2000" />
    <!-- 线程池对拒绝任务(无线程可用)的处理策略 ThreadPoolExecutor.CallerRunsPolicy策略 ,调用者的线程会执行该任务,如果执行器已关闭,则丢弃.  -->
    <property name="rejectedExecutionHandler">
        <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
    </property>
</bean>

<!--<bean id ="jobFactory" class = "com.fufulong.quarzbean.JobFactory"/>-->

<context:component-scan base-package="com.fufulong"/>

这里再说明一下2个问题:
第一个就是Quartz的定时任务线程池,当线程池中的所有线程都是忙碌状态的时候,这时候,还有新的定时任务需要取得线程执行的时候,可以有以下几种处理策略
1),java.util.concurrent.ThreadPoolExecutor C a l l e r R u n s P o l i c y : 表 示 由 掉 用 定 时 任 务 的 主 线 程 执 行 2 ) , j a v a . u t i l . c o n c u r r e n t . T h r e a d P o o l E x e c u t o r CallerRunsPolicy: 表示由掉用定时任务的主线程执行 2),java.util.concurrent.ThreadPoolExecutor CallerRunsPolicy:表示由掉用定时任务的主线程执行2),java.util.concurrent.ThreadPoolExecutorAbortPolicy: 表示直接拒绝执行,并报出异常
3),java.util.concurrent.ThreadPoolExecutor D i s c a r d P o l i c y : 表 示 放 弃 线 程 池 中 的 目 前 正 在 执 行 的 最 后 一 个 开 始 执 行 的 定 时 任 务 , 执 行 我 们 新 加 的 这 个 定 时 任 务 4 ) , j a v a . u t i l . c o n c u r r e n t . T h r e a d P o o l E x e c u t o r DiscardPolicy: 表示放弃线程池中的目前正在执行的最后一个开始执行的定时任务,执行我们新加的这个定时任务 4),java.util.concurrent.ThreadPoolExecutor DiscardPolicy:表示放弃线程池中的目前正在执行的最后一个开始执行的定时任务,执行我们新加的这个定时任务4),java.util.concurrent.ThreadPoolExecutorDiscardOldestPolicy; 和上一个差不多,只是这个策略表示要放弃线程池中的最早开始执行的定时任务,转而空出一个线程执行新的定时任务.
上面的策略一般用第1种或者第2种,第3种和第4种不建议使用,因为直接放弃还没有执行完毕的定时任务很有可能 会引发很多问题.

第二个就是,实际上Spring 的Bean的容器和 Quartz的调度器的容器是2个独立的容器,所以在定时器的Job中使用@Autowired引入Spring Context管理的其他Bean,执行定时任务的时候,这个属性对象一定是null,所以为了解决这个问题,需要做一些处理.需要写一个配置类,为Spring容器和Job之间架起一道桥,让Job也能使用Spring的Bean.
配置一个JobFactory bean,并让Quartz的任务调度器引用这个factoryBean,如上面的配置文件所示
package com.fufulong.quarzbean;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;

@Component
public class JobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory capableBeanFactory ;

@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
    Object jobInstance = super.createJobInstance(bundle);

    capableBeanFactory.autowireBean(jobInstance);

    return jobInstance;
}

}

4.最复杂也是最灵活的方式,使用数据库作为定时任务信息保存定时任务相关信息(job,jobdetail,trigger,Schedulefactory正在执行和暂停的任务等)
首先,Quartz的主要配置信息可以写在一个配置文件中quartz.properties, 这个文件的内容如下所示:
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false

实例化ThreadPool时,使用的线程类为SimpleThreadPool

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

threadCount和threadPriority将以setter的形式注入ThreadPool实例

并发个数

org.quartz.threadPool.threadCount = 10

优先级

org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

org.quartz.jobStore.misfireThreshold = 5000

默认存储在内存中

#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

#持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

这个表前缀一定要和数据库中quartz先关的11张表的前缀保持一致

org.quartz.jobStore.tablePrefix = qrtz__

注意这里的数据源名称一定要和下面保持一致

org.quartz.jobStore.dataSource = qzDS

org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver

制定先关的11张表存在数据库驱动地址

org.quartz.dataSource.qzDS.URL = jdbc:mysql://localhost:3306/qzDS?useUnicode=true&characterEncoding=UTF-8

org.quartz.dataSource.qzDS.user = root

org.quartz.dataSource.qzDS.password = root

org.quartz.dataSource.qzDS.maxConnections = 10

这里一定要注意,当使用是自己定义的Quartz的配置文件时候,一定要写上这句,不然会报错

org.quartz.jobStore.driverDelegateClass = org.quartz.impldbcjobstore.StdJDBCDelegate

注意: 当使用是自己定义的Quartz的配置文件时候,一定要写上这句,不然会报错
org.quartz.jobStore.driverDelegateClass = org.quartz.impldbcjobstore.StdJDBCDelegate
然后要在对应上面的驱动地址的mysql数据库quarz中创建11张表, 创建这11张表的SQL语句如下所示:

drop table if exists qrtzJzl_FIRED_TRIGGERS;
drop table if exists qrtzJzl_PAUSED_TRIGGER_GRPS;
drop table if exists qrtzJzl_SCHEDULER_STATE;
drop table if exists qrtzJzl_LOCKS;
drop table if exists qrtzJzl_SIMPLE_TRIGGERS;
drop table if exists qrtzJzl_SIMPROP_TRIGGERS;
drop table if exists qrtzJzl_CRON_TRIGGERS;
drop table if exists qrtzJzl_BLOB_TRIGGERS;
drop table if exists qrtzJzl_TRIGGERS;
drop table if exists qrtzJzl_JOB_DETAILS;
drop table if exists qrtzJzl_CALENDARS;

– 1
CREATE TABLE qrtzJzl_JOB_DETAILS (
sched_name varchar(60) NOT NULL,
job_name varchar(60) NOT NULL,
job_group varchar(60) NOT NULL,
description varchar(250) DEFAULT NULL,
job_class_name varchar(250) NOT NULL,
is_durable varchar(1) NOT NULL,
is_nonconcurrent varchar(1) NOT NULL,
is_update_data varchar(1) NOT NULL,
requests_recovery varchar(1) NOT NULL,
job_data blob,
PRIMARY KEY (sched_name,job_name,job_group)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

– 2
CREATE TABLE qrtzJzl_TRIGGERS (
sched_name varchar(60) NOT NULL,
trigger_name varchar(60) NOT NULL,
trigger_group varchar(60) NOT NULL,
job_name varchar(60) NOT NULL,
job_group varchar(60) NOT NULL,
description varchar(250) DEFAULT NULL,
next_fire_time bigint(13) DEFAULT NULL,
prev_fire_time bigint(13) DEFAULT NULL,
priority int(11) DEFAULT NULL,
trigger_state varchar(16) NOT NULL,
trigger_type varchar(8) NOT NULL,
start_time bigint(13) NOT NULL,
end_time bigint(13) DEFAULT NULL,
calendar_name varchar(200) DEFAULT NULL,
misfire_instr smallint(2) DEFAULT NULL,
job_data blob,
PRIMARY KEY (sched_name,trigger_name,trigger_group),
KEY sched_name (sched_name,job_name,job_group),
CONSTRAINT qrtzJzl_TRIGGERS_ibfk_1 FOREIGN KEY (sched_name, job_name, job_group) REFERENCES qrtzJzl_JOB_DETAILS (sched_name, job_name, job_group)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

– 3
CREATE TABLE qrtzJzl_SIMPLE_TRIGGERS (
sched_name varchar(60) NOT NULL,
trigger_name varchar(60) NOT NULL,
trigger_group varchar(60) NOT NULL,
repeat_count bigint(7) NOT NULL,
repeat_interval bigint(12) NOT NULL,
times_triggered bigint(10) NOT NULL,
PRIMARY KEY (sched_name,trigger_name,trigger_group),
CONSTRAINT qrtzJzl_SIMPLE_TRIGGERS_ibfk_1 FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES qrtzJzl_TRIGGERS (sched_name, trigger_name, trigger_group)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

– 4
CREATE TABLE qrtzJzl_CRON_TRIGGERS (
sched_name varchar(60) NOT NULL,
trigger_name varchar(60) NOT NULL,
trigger_group varchar(60) NOT NULL,
cron_expression varchar(200) NOT NULL,
time_zone_id varchar(80) DEFAULT NULL,
PRIMARY KEY (sched_name,trigger_name,trigger_group),
CONSTRAINT qrtzJzl_CRON_TRIGGERS_ibfk_1 FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES qrtzJzl_TRIGGERS (sched_name, trigger_name, trigger_group)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

– 5
CREATE TABLE qrtzJzl_SIMPROP_TRIGGERS (
sched_name varchar(60) NOT NULL,
trigger_name varchar(60) NOT NULL,
trigger_group varchar(60) NOT NULL,
str_prop_1 varchar(512) DEFAULT NULL,
str_prop_2 varchar(512) DEFAULT NULL,
str_prop_3 varchar(512) DEFAULT NULL,
int_prop_1 int(11) DEFAULT NULL,
int_prop_2 int(11) DEFAULT NULL,
long_prop_1 bigint(20) DEFAULT NULL,
long_prop_2 bigint(20) DEFAULT NULL,
dec_prop_1 decimal(13,4) DEFAULT NULL,
dec_prop_2 decimal(13,4) DEFAULT NULL,
bool_prop_1 varchar(1) DEFAULT NULL,
bool_prop_2 varchar(1) DEFAULT NULL,
PRIMARY KEY (sched_name,trigger_name,trigger_group),
CONSTRAINT qrtzJzl_SIMPROP_TRIGGERS_ibfk_1 FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES qrtzJzl_TRIGGERS (sched_name, trigger_name, trigger_group)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

– 6

CREATE TABLE qrtzJzl_BLOB_TRIGGERS (
sched_name varchar(60) NOT NULL,
trigger_name varchar(60) NOT NULL,
trigger_group varchar(60) NOT NULL,
blob_data blob,
PRIMARY KEY (sched_name,trigger_name,trigger_group),
CONSTRAINT qrtzJzl_BLOB_TRIGGERS_ibfk_1 FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES qrtzJzl_TRIGGERS (sched_name, trigger_name, trigger_group)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

– 7
CREATE TABLE qrtzJzl_CALENDARS (
sched_name varchar(60) NOT NULL,
calendar_name varchar(60) NOT NULL,
calendar blob NOT NULL,
PRIMARY KEY (sched_name,calendar_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

– 8
CREATE TABLE qrtzJzl_PAUSED_TRIGGER_GRPS (
sched_name varchar(60) NOT NULL,
trigger_group varchar(60) NOT NULL,
PRIMARY KEY (sched_name,trigger_group)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

– 9
CREATE TABLE qrtzJzl_FIRED_TRIGGERS (
sched_name varchar(60) NOT NULL,
entry_id varchar(60) NOT NULL,
trigger_name varchar(200) NOT NULL,
trigger_group varchar(200) NOT NULL,
instance_name varchar(200) NOT NULL,
fired_time bigint(13) NOT NULL,
sched_time bigint(13) NOT NULL,
priority int(11) NOT NULL,
state varchar(16) NOT NULL,
job_name varchar(200) DEFAULT NULL,
job_group varchar(200) DEFAULT NULL,
is_nonconcurrent varchar(1) DEFAULT NULL,
requests_recovery varchar(1) DEFAULT NULL,
PRIMARY KEY (sched_name,entry_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

– 10
CREATE TABLE qrtzJzl_SCHEDULER_STATE (
sched_name varchar(60) NOT NULL,
instance_name varchar(60) NOT NULL,
last_checkin_time bigint(13) NOT NULL,
checkin_interval bigint(13) NOT NULL,
PRIMARY KEY (sched_name,instance_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

– 11
CREATE TABLE qrtzJzl_LOCKS (
sched_name varchar(60) NOT NULL,
lock_name varchar(60) NOT NULL,
PRIMARY KEY (sched_name,lock_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这11张表是有关联关系的,并且JDBC表的插入顺序是
Simple_trigger :插入顺序
qrtz_job_details —> qrtz_triggers —> qrtz_simple_triggers
qrtz_fired_triggers
Cron_Trigger:插入顺序
qrtz_job_details —> qrtz_triggers —> qrtz_cron_triggers
qrtz_fired_triggers

qrtz_triggers 表中trigger_state的值有:
WAITING:等待
PAUSED:暂停
ACQUIRED:正常执行
BLOCKED:阻塞
ERROR:错误

. 表关系
使用Quartz的4种方式

.表解释
使用Quartz的4种方式

然后在Spring 主配置文件中配置数据源,并整合mybatis,然后再componen-scan扫描的包路径下创建配置类 JobFactory 和 QuartzConfig 配置类.
. 项目路径截图:
使用Quartz的4种方式

. applicationContext4.xml的内容

<?xml version="1.0" encoding="UTF-8"?>

<context:component-scan base-package="com.fufulong.jdbcSerlizedType"/>

<!-- 引入外置文件 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location" value="classpath:jdbc.properties"/>
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
      init-method="init" destroy-method="close">
    <!-- 数据源驱动类可不写,Druid默认会自动根据URL识别DriverClass -->
    <property name="driverClassName" value="${jdbc.driver}"/>

    <!-- 基本属性 url、user、password -->
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>

    <!-- 配置初始化大小、最小、最大 -->
    <property name="initialSize" value="5"/>
    <property name="minIdle" value="5"/>
    <property name="maxActive" value="15"/>

    <!-- 配置获取连接等待超时的时间 -->
    <property name="maxWait" value="60000"/>

    <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
    <property name="timeBetweenEvictionRunsMillis" value="60000"/>

    <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
    <property name="minEvictableIdleTimeMillis" value="300000"/>

    <property name="validationQuery" value="select 1 from dual"/>
    <property name="testWhileIdle" value="true"/>
    <property name="testOnBorrow" value="false"/>
    <property name="testOnReturn" value="false"/>
    <!-- 打开PSCache,并且指定每个连接上PSCache的大小(Oracle使用) -->
    <!-- <property name="poolPreparedStatements"
               value="true"/>
     <property name="maxPoolPreparedStatementPerConnectionSize"
               value="20"/>-->
    <!-- 配置监控统计拦截的filters -->
    <property name="filters" value="stat"/>
</bean>


<!--配置SqlsessionfactoryBean ,就是整合Spring与mybatis-->
<bean id ="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!--配置数据库-->
    <property name="dataSource" ref="dataSource"/>
    <!-- 配置mybatis的主配置文件-->
    <property name="configLocation" value = "classpath:mybatis-config.xml"/>
    <property name="mapperLocations" value ="classpath:mapper/*.xml"/>
    <property name="plugins">
        <array>
            <bean class="com.github.pagehelper.PageHelper">
                <property name="properties">
                    <value>
                        dialect=mysql
                    </value>
                </property>
            </bean>
        </array>
    </property>

</bean>
<!--配置 mapperScanconfig-->
<bean id="mapperScanConfig" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com/fufulong/jdbcSerlizedType/dao"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

<!--配置事务管理器(mybatis采用的是JDBC的事务管理器)-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>

</bean>

<!--配置基于注解的声明式事务,默认使用注解来管理事务行为-->
<tx:annotation-driven transaction-manager="transactionManager"/>

. JobFactory 类
package com.fufulong.jdbcSerlizedType.config;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;

@Component(value = “JobFactory”)
public class JobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory capableBeanFactory ;

@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
    Object jobInstance = super.createJobInstance(bundle);

    capableBeanFactory.autowireBean(jobInstance);

    return jobInstance;
}

}

. QuartzConfig类
package com.fufulong.jdbcSerlizedType.config;

import org.quartz.Scheduler;
import org.quartz.ee.servlet.QuartzInitializerListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import java.io.IOException;
import java.util.Properties;

@Configuration
public class ScheduleConfig {
@Autowired
public JobFactory jobFactory;

/**
 * 1.先配置 quarz.properties
 */
@Bean
public Properties quartzProperties() throws IOException {
    PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
    propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
    //在quartz.properties中的属性被读取并注入后再初始化对象
    propertiesFactoryBean.afterPropertiesSet();
    return propertiesFactoryBean.getObject();
}

/**
 * 2. 配置 SchedulefactorBean
 */
@Bean
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
    SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
    factoryBean.setQuartzProperties(quartzProperties());
    factoryBean.setJobFactory(jobFactory);
    return factoryBean;
}

/**
 * 配置Quartz的监听器,这样如果是在 Web项目中,可以在web容器启动的时候,一起启动Quautz
 * @return
 */
@Bean
public QuartzInitializerListener executorListener() {
    return new QuartzInitializerListener();
}

/*
 * 通过SchedulerFactoryBean获取Scheduler的实例
 */
@Bean(name="Scheduler")
public Scheduler scheduler() throws IOException {
    return schedulerFactoryBean().getScheduler();
}

}

配置完毕之后,能够得到一个重要的由Spring管理的bean : schedule, 下面我们可以使用这个对象,结合schedule的API,创建一个服务类,用来创建,删除,暂停,调整task的 cronTrigger的cron表达式,查询正在运行的task等,TaslServiceImple代码如下:
package com.fufulong.jdbcSerlizedType.service.impl;

import com.fufulong.jdbcSerlizedType.dao.JobAndTriggerMapper;
import com.fufulong.jdbcSerlizedType.pojo.JobAndTrigger;
import com.fufulong.jdbcSerlizedType.service.TaskService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class TaskServiceImpl implements TaskService {
@Autowired
private JobAndTriggerMapper jobAndTriggerMapper;
@Autowired
@Qualifier(value =“Scheduler”)
private Scheduler scheduler;

@Override
public void addTask(Class<? extends Job> jobClazz, String name, String groupName, String cronExpression) {
    try {
        scheduler.start();
        // 启动调度器
        scheduler.start();

        //构建job信息
        JobDetail jobDetail = JobBuilder.newJob(jobClazz).withIdentity(name, groupName).build();

        //表达式调度构建器(即任务执行的时间)
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);

        //按新的cronExpression表达式构建一个新的trigger
        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(name, groupName)
                .withSchedule(scheduleBuilder).build();
        scheduler.scheduleJob(jobDetail, trigger);

    } catch (SchedulerException e) {
        e.printStackTrace();
    }
}

@Override
public void deleteTask(String name, String groupName) {
    try {
        scheduler.pauseTrigger(TriggerKey.triggerKey(name, groupName));
        scheduler.unscheduleJob(TriggerKey.triggerKey(name, groupName));
        scheduler.deleteJob(JobKey.jobKey(name, groupName));
    } catch (SchedulerException e) {
        e.printStackTrace();
    }
}
// 暂停定时任务
@Override
public void pauseTask(String name, String groupName) {
    try {
        scheduler.pauseJob(new JobKey(name,groupName));
    } catch (SchedulerException e) {
        e.printStackTrace();
    }
}

//恢复暂停的定时任务
@Override
public void resumeTask(String name, String groupName) {
try {
scheduler.resumeJob(JobKey.jobKey(name,groupName));
} catch (SchedulerException e) {
e.printStackTrace();
}
}
//调整已经存在的定时任务的Trigger
@Override
public void reScheduleTask(String name, String groupName, String cronExpression) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(name, groupName);
CronTrigger trigger = null;
trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 按新的cronExpression表达式重新构建trigger
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();

        // 按新的trigger重新设置job执行
        scheduler.rescheduleJob(triggerKey, trigger);

    } catch (SchedulerException e) {
        e.printStackTrace();
    }

}

//分页查询正在正常运行的定时任务
@Override
public PageInfo query(Integer pageNum, Integer pageSize) {
PageHelper.startPage(pageNum,pageSize);
List list = jobAndTriggerMapper.queryJobs();
PageInfo pageInfo = new PageInfo<>(list);
return pageInfo;
}
}

所有的任务类都实现Job接口,(QuartzBean 抽象类也实现了Job接口),并重写execute方法
Component
public class Task1 implements Job {
@Autowired
private TestServiceImpl testService;

@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    System.out.println("task1 的 任务:" + format.format(new Date()) );
}

}

.mapper的xml文件中查询定时任务的方法

<?xml version="1.0" encoding="UTF-8"?> select QRTZ_JOB_DETAILS.job_name as name, QRTZ_JOB_DETAILS.job_group as groupname, QRTZ_JOB_DETAILS.job_class_name as jobclassname, QRTZ_CRON_TRIGGERS.cron_expression as cronexpression

from
QRTZ_JOB_DETAILS
inner join QRTZ_TRIGGERS
inner join QRTZ_CRON_TRIGGERS
on QRTZ_JOB_DETAILS.job_name = QRTZ_TRIGGERS.job_name
and QRTZ_TRIGGERS.trigger_name = QRTZ_CRON_TRIGGERS.trigger_name
and QRTZ_TRIGGERS.trigger_group = QRTZ_CRON_TRIGGERS.trigger_group

</select>

本文主要参考的文章如下:
https://blog.csdn.net/u010648555/article/details/54863144
https://blog.csdn.net/u012907049/article/details/73801122

项目已经上传到我的 gItHub,地址:

上一篇:MySQL触发器使用


下一篇:trigger:使用structuredStreaming实时计算