Quartz 定时任务使用 —— 定时任务持久化到数据库(十四)

Quartz 定时任务使用

用数据库存储定时任务信息

之前的文章所做的demo是将定时任务的信息保存在内存(RAM)中的,见以下配置

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

如果用内存记录定时任务信息,应用重新启动后,定时任务信息将会丢失。比如,用户A通过系统设置1小时后执行Z操作,设置好后的,因系统重新启动,新启动的系统将会丢失“1小时后执行Z操作”的定时任务。

如果,我们需要在系统意外(或非意外)重新启动后,仍保留定时任务信息,可以使用数据库存储定时任务信息。

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

默认情况下,Quartz使用RAMJobStore实现,它简单的把任务放在内存中。

其他可用实现是JobStoreCMT和JobStoreTX。这两个类都使用一个配置好的数据源来持久化任务细节,这就支持将任务的创建和修改作为事务的一部分。如果你要和你的应用容器一起管理,那你可以使用quartz的JobStoreCMT,quartz通过JobStoreCMT来的使用来让你的应用容器管理quartz的事务

Spring为JobStore提供了自己的LocalDataSourceJobStore实现,它可以参加Spring管理的事务。当我们讨论Spring对Quartz支持时我们会关注该实现。

以MySQL为例子,做个简单的DEMO

由于需要连接MySQL数据库,需要加上数据库的JDBC驱动,这里以pom形式下载,也可以直接引入包

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.35</version>
</dependency>

建立数据存储表

因为需要把quartz的数据保存到数据库,所以要建立相关的数据库。这个可以从下载到的quartz包里面找到对应的sql脚本,目前可以支持mysql,DB2,oracle等主流的数据库,自己可以根据项目需要选择合适的脚本运行。脚本文件在\docs\dbTables目录下

屏幕快照 2017-09-14 15.09.56.png

quartz数据表含义

各个表中的字段,约束,主外建关联含义,请阅读文章:Quartz 定时任务使用 —— 数据库各表字段的含义(十七)

表名含义
qrtz_calendars以 Blob 类型存储 Quartz 的 Calendar 信息
qrtz_cron_triggers存储 Cron Trigger,包括Cron表达式和时区信息
qrtz_fired_triggers存储与已触发的 Trigger 相关的状态信息,以及相联 Job的执行信息QRTZ_PAUSED_TRIGGER_GRPS 存储已暂停的 Trigger组的信息
qrtz_scheduler_state存储少量的有关 Scheduler 的状态信息,和别的Scheduler实例(假如是用于一个集群中)
qrtz_locks存储程序的悲观锁的信息(假如使用了悲观锁)
qrtz_job_details存储每一个已配置的 Job 的详细信息
qrtz_simple_triggers存储简单的Trigger,包括重复次数,间隔,以及已触的次数 
qrtz_simprop_triggers
qrtz_blob_triggersTrigger 作为 Blob 类型存储(用于 Quartz 用户用JDBC创建他们自己定制的 Trigger 类型,JobStore并不知道如何存储实例的时候) 
qrtz_triggers触发器的基本信息 
qrtz_paused_trigger_graps存放暂停掉的触发器。

主要的几张表:

qrtz_job_details:保存job详细信息,该表需要用户根据实际情况初始化 

  • job_name:集群中job的名字,可以随意定制

  • job_group:集群中job的所属组的名字,可以随意定制

  • job_class_name:集群中个notejob实现类的完全包名,quartz就是根据这个路径到classpath找到该job类 

  • is_durable:是否持久化,把该属性设置为1,quartz会把job持久化到数据库中 

  • job_data:一个blob字段,存放持久化job对象 

qrtz_triggers:保存trigger信息 

  • trigger_name:trigger的名字,可以随意定制

  • trigger_group:trigger所属组的名字,可以随意定制

  • job_name:qrtz_job_details表job_name的外键 

  • job_group:qrtz_job_details表job_group的外键 

  • trigger_state:当前trigger状态,设置为ACQUIRED,如果设置为WAITING,则job不会触发 

  • trigger_cron:触发器类型,使用cron表达式

qrtz_cron_triggers:存储cron表达式表 

  • trigger_name:qrtz_triggers表trigger_name的外键 

  • trigger_group:qrtz_triggers表trigger_group的外键 

  • cron_expression:cron表达式 

  • qrtz_scheduler_state:存储集群中note实例信息,quartz会定时读取该表的信息判断集群中每个实例的当前状态 

  • instance_name:之前配置文件中org.quartz.scheduler.instanceId配置的名字,就会写入该字段,如果设置为AUTO,quartz会根据物理机名和当前时间产生一个名字 

  • last_checkin_time:上次检查时间 

  • checkin_interval:检查间隔时间 

注:你可能也注意到了,这些表都是以QRTZ_为前缀的,这是默认的前缀。如果你需要用到其他前缀(个性化需求,或需要配置多个quartz实例),可以在以下项配置(在quartz.properties中)

org.quartz.jobStore.tablePrefix = QRTZ_

最主要的修改是quartz.properties

org.quartz.scheduler.instanceName = MyScheduler
org.quartz.threadPool.threadCount = 20

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.dataSource = myDS

org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/myDataBase?characterEncoding=utf-8
org.quartz.dataSource.myDS.user = root
org.quartz.dataSource.myDS.password = 123456
org.quartz.dataSource.myDS.maxConnections = 10

而org.quartz.jobStore.driverDelegateClass是根据选择的数据库不同而改变,mysql对应的是org.quartz.impl.jdbcjobstore.StdJDBCDelegate.

如果你使用其他数据库,可以选择不同的代理类(在org.quar.impl.jdbcjobstore package或者其子包中可以找到):包括DB2v6Delegate (for DB2 version 6 and earlier)、HSQLDBDelegate (for HSQLDB)、MSSQLDelegate (for Microsoft SQLServer)、PostgreSQLDelegate (for PostgreSQL)、WeblogicDelegate (for using JDBC drivers made by WebLogic)、OracleDelegate (for using Oracle)等等.

创建表后,在配置和启动JDBCJobStore之前,您还有一个重要的决定。 您需要确定应用程序需要哪种类型的事务。 如果您不需要将调度命令(例如添加和删除触发器)绑定到其他事务,那么可以通过使用JobStoreTX作为JobStore来管理事务(这是最常见的选择)。

如果您需要Quartz与其他事务(即在J2EE应用程序服务器中)一起工作,那么您应该使用JobStoreCMT - 在这种情况下,Quartz将让应用程序服务器容器管理事务。

最后一个难题是设置一个DataSource,JDBCJobStore可以从中获取与数据库的连接。 DataSources在Quartz属性中使用几种不同的方法之一进行定义。 一种方法是让Quartz创建和管理DataSource本身 - 通过提供数据库的所有连接信息。 另一种方法是让Quartz使用由Quartz正在运行的应用程序服务器管理的DataSource,通过提供JDBCJobStore DataSource的JNDI名称。 有关属性的详细信息,请参阅“docs / config”文件夹中的示例配置文件。

要使用JDBCJobStore(并假定您使用的是StdSchedulerFactory),首先需要将Quartz配置的JobStore类属性设置为org.quartz.impl.jdbcjobstore.JobStoreTX 或 org.quartz.impl.jdbcjobstore.JobStoreCMT

将“org.quartz.jobStore.useProperties”配置参数设置为“true”(默认为false),以表示JDBCJobStore将JobDataMaps中的所有值都作为字符串,因此可以作为名称 - 值对存储 而不是在BLOB列中以其序列化形式存储更多复杂的对象。 从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题。

完整代码示例

示例中涉及的文件不多,一个MyJob任务执行,SimpleExample测试,MyPoolingconnectionProvider数据源连接和quartz.properties配置文件。

数据源是自己定义的类,实现了quartz自带的ConnectionProvider类,如果不想使用它,你也可以选择其他数据源,比如Tomcat的DataSource,Spring的SimpleDriverDataSource等。自行选择。

org.quartz.dataSource.myDS.connectionProvider.class : com.anson.examples.jdbc_example.MyPoolingconnectionProvider
#org.quartz.dataSource.myDS.connectionProvider.class : org.springframework.jdbc.datasource.SimpleDriverDataSource
#org.quartz.dataSource.myDS.connectionProvider.class : org.apache.tomcat.jdbc.pool.DataSource

MyJob.java

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;

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

public class MyJob implements Job {

    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobKey jobKey = context.getJobDetail().getKey();

        System.out.println("\n任务key " + jobKey + "执行时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }

}

SimpleExample.java

mport org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.triggers.SimpleTriggerImpl;

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

public class SimpleExample {

    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        try {
            
            System.out.println("------- 初始化 ----------------------");

            //通过调度器工厂获取调度器,初始化工程时须指定其使用我们自己的配置文件
            SchedulerFactory sf = new StdSchedulerFactory("quartz.properties");
            Scheduler sched = sf.getScheduler();

            //这儿clear一下,因为使用数据库储存方式时,shutdown的时候没有清除,第二次运行会报Job is already exist
            sched.clear();

            System.out.println("------- 初始化完成 -----------");

            // 下一分钟开始执行
            Date runTime = DateBuilder.evenMinuteDate(new Date());

            System.out.println("------- Scheduling Job  -------------------");

            // 任务详情
            JobDetail job = JobBuilder.newJob(MyJob.class).withIdentity("job1", "group1").build();

            // 触发器 重复5+1次 间隔15秒
            SimpleTriggerImpl trigger