用数据库存储定时任务信息
之前的文章所做的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目录下
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_triggers | Trigger 作为 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 = (SimpleTriggerImpl) TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build();
trigger.setRepeatCount(5);
trigger.setRepeatInterval(15000);
System.out.println("------- 当前时间:" + sdf.format(new Date())+ " -----------------");
//调度器、触发器、任务,三者关联
sched.scheduleJob(job, trigger);
System.out.println(job.getKey() + " 开始job运行时间:" + sdf.format(runTime));
//调度启动
sched.start();
System.out.println("------- 开始调度器 Scheduler -----------------");
System.out.println("------- 等待5分钟... -------------");
try {
Thread.sleep(5*60000L);
} catch (Exception e) {
}
System.out.println("------- 关闭调度器 ---------------------");
sched.shutdown(true);
System.out.println("------- 关闭完成 -----------------");
SchedulerMetaData metaData = sched.getMetaData();
System.out.println("~~~~~~~~~~ 执行了 " + metaData.getNumberOfJobsExecuted() + " 个 jobs.");
} catch (Exception e) {
e.printStackTrace();
}
}
}MyPoolingconnectionProvider.java
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.quartz.utils.ConnectionProvider;
import java.sql.Connection;
import java.sql.SQLException;
public class MyPoolingconnectionProvider implements ConnectionProvider {
private String driver;
private String url;
private String user;
private String password;
private int maxConnections;
private ComboPooledDataSource datasource = new ComboPooledDataSource();
/**
* 连接
*
* @return
* @throws SQLException
*/
@Override
public Connection getConnection() throws SQLException {
System.out.println("获取连接");
return datasource.getConnection();
}
/**
* 关闭连接
*
* @throws SQLException
*/
@Override
public void shutdown() throws SQLException {
System.out.println("关闭连接");
datasource.close();
}
/**
* 初始化
*
* @throws SQLException
*/
@Override
public void initialize() throws SQLException {
try {
System.out.println("初始化连接");
datasource.setDriverClass(this.driver);
datasource.setJdbcUrl(this.url);
datasource.setUser(this.user);
datasource.setPassword(this.password);
datasource.setMaxPoolSize(this.maxConnections);
datasource.setMinPoolSize(1);
} catch (Exception e) {
e.printStackTrace();
}
}
/*-------------------------------------------------
*
* setters 如果有必要,你可以添加一些getter
* ------------------------------------------------
*/
public void setDriver(String driver) {
this.driver = driver;
}
public void setUrl(String url) {
this.url = url;
}
public void setUser(String user) {
this.user = user;
}
public void setPassword(String password) {
this.password = password;
}
public void setMaxConnections(int maxConnections) {
this.maxConnections = maxConnections;
}
public void setDatasource(ComboPooledDataSource datasource) {
this.datasource = datasource;
}
}quartz.properties
############################################ ############# 调度器配置 ############ ############################################ org.quartz.scheduler.instanceName : MyQuartzScheduler org.quartz.threadPool.threadCount : 25 org.quartz.scheduler.rmi.export: false org.quartz.scheduler.rmi.proxy: false org.quartz.scheduler.wrapJobExecutionInUserTransaction: false org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadPriority: 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true ############################################ ############# 持久化配置 ############ ############################################ #org.quartz.jobStore.class : org.quartz.simpl.RAMJobStore 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.jobStore.useProperties : true org.quartz.jobStore.misfireThreshold: 60000 ############################################ ############# 数据库连接 ############ ############################################ org.quartz.dataSource.myDS.connectionProvider.class : com.anson.examples.jdbc_example.MyPoolingconnectionProvider org.quartz.dataSource.myDS.driver : com.mysql.jdbc.Driver org.quartz.dataSource.myDS.url : jdbc:mysql://localhost:3306/myDatabase?useUnicode=true&characterEncoding=utf-8 org.quartz.dataSource.myDS.user : chengxumiao org.quartz.dataSource.myDS.password : chengxumiao org.quartz.dataSource.myDS.maxConnections : 10
执行结果
[INFO] 14 九月 09:28:31.672 下午 main [org.quartz.impl.StdSchedulerFactory]
Quartz scheduler version: 2.2.3
获取连接
[INFO] 14 九月 09:28:31.731 下午 main [com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource]
Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> 1hge6xt9qzobdptp7huv|3abfe836, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 1hge6xt9qzobdptp7huv|3abfe836, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/myDatabase?useUnicode=true&characterEncoding=utf-8, lastAcquisitionFailureDefaultUser -> null, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 10, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 1, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> null, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false ]
------- 初始化完成 -----------
------- Scheduling Job -------------------
------- 当前时间:2017-09-14 21:28:32 -----------------
获取连接
group1.job1 开始job运行时间:2017-09-14 21:29:00
获取连接
[INFO] 14 九月 09:28:32.202 下午 main [org.quartz.impl.jdbcjobstore.JobStoreTX]
Freed 0 triggers from 'acquired' / 'blocked' state.
[INFO] 14 九月 09:28:32.204 下午 main [org.quartz.impl.jdbcjobstore.JobStoreTX]
Recovering 0 jobs that were in-progress at the time of the last shut-down.
[INFO] 14 九月 09:28:32.204 下午 main [org.quartz.impl.jdbcjobstore.JobStoreTX]
Recovery complete.
[INFO] 14 九月 09:28:32.205 下午 main [org.quartz.impl.jdbcjobstore.JobStoreTX]
Removed 0 'complete' triggers.
[INFO] 14 九月 09:28:32.206 下午 main [org.quartz.impl.jdbcjobstore.JobStoreTX]
Removed 0 stale fired job entries.
获取连接
[INFO] 14 九月 09:28:32.207 下午 main [org.quartz.core.QuartzScheduler]
Scheduler MyQuartzScheduler_$_NON_CLUSTERED started.
------- 开始调度器 Scheduler -----------------
------- 等待5分钟... -------------
获取连接
获取连接
获取连接
任务key group1.job1执行时间:2017-09-14 21:29:00
获取连接
获取连接
获取连接
任务key group1.job1执行时间:2017-09-14 21:29:15
获取连接
获取连接
获取连接
任务key group1.job1执行时间:2017-09-14 21:29:30
获取连接
获取连接
获取连接
获取连接
任务key group1.job1执行时间:2017-09-14 21:29:45
获取连接
获取连接
获取连接
任务key group1.job1执行时间:2017-09-14 21:30:00
获取连接
获取连接
获取连接
任务key group1.job1执行时间:2017-09-14 21:30:15
获取连接
获取连接
获取连接
获取连接
获取连接
获取连接
获取连接
获取连接
获取连接
获取连接
获取连接
------- 关闭调度器 ---------------------
[INFO] 14 九月 09:33:32.211 下午 main [org.quartz.core.QuartzScheduler]
Scheduler MyQuartzScheduler_$_NON_CLUSTERED shutting down.
[INFO] 14 九月 09:33:32.211 下午 main [org.quartz.core.QuartzScheduler]
Scheduler MyQuartzScheduler_$_NON_CLUSTERED paused.
获取连接
关闭连接
[INFO] 14 九月 09:33:32.618 下午 main [org.quartz.core.QuartzScheduler]
Scheduler MyQuartzScheduler_$_NON_CLUSTERED shutdown complete.
------- 关闭完成 -----------------
~~~~~~~~~~ 执行了 6 个 jobs.运行示例时,注意观察数据库表的变化,新创建好的11张表中是没有数据的。
当任务创建好之后,如果不能立即执行,那么会将此任务存储在表中,时间到后,任务执行过会将此任务的相关表数据删除。
简单的10个字概括就是:未执行,插入; 执行过,删除。
未经允许请勿转载:程序喵 » Quartz 定时任务使用 —— 定时任务持久化到数据库(十四)
程序喵