概述
在默认情况下,Quartz将任务调度的运行信息保存在内存中。 这种方法提供了最佳的性能,因为在内存中的数据访问速度最快;不足之处就是却反数据的持久性,当程序中途停止或者系统崩溃时,所有运行的信息都会丢失。
比如我们希望安排一个执行100次的任务,如果执行到50次时系统崩溃了,系统重启时任务的执行计数器将从0开始。在大多数实际的应用中,我们往往并不需要保存任务调度的现场数据,因为很少需要规划一个指定执行次数的任务。对于仅执行一次的任务来说,其执行条件信息本身应该是已经持久化的业务数据,当执行完成后,条件信息也会相应改变。当然调度现场信息不仅仅是记录运行次数,还包括调度规则、JobDataMap中的数据等等。
如果确实需要持久化任务调度信息,Quartz允许你通过调整其属性文件,将这些信息保存到数据库中。使用数据库保存任务调度信息后,即使系统崩溃后重新启动,任务的调度信息将得到恢复。如前面所说的例子,执行50次崩溃后重新运行,计数器将从51开始计数。使用了数据库保存信息的任务称为持久化任务。
操作步骤
执行脚本建立对应的表
方式一: 从下载的压缩包中找 quartz-2.2.3\docs\dbTables中
方式二:https://github.com/quartz-scheduler/quartz/tree/quartz-1.8.x/docs/dbTables
这里我们使用Oracle数据库,我们执行tables_oracle.sql即可,共计11个表。
quartz数据表解释
配置quartz.properties
首先,我们需要在我们的属性文件中表明使用JobStoreTX:
org.quartz.jobStore.class = org.quartz.ompl.jdbcjobstore.JobStoreTX
然后我们需要配置能理解不同数据库系统中某一特定方言的驱动代理,选择选择对应数据库的代理类。
这些代理类在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)等
然后确定应用程序需要哪种类型的事务。 如果不需要将调度命令(例如添加和删除触发器)绑定到其他事务,那么可以通过使用JobStoreTX作为JobStore来管理事务(这是最常见的选择)。
如果需要Quartz与其他事务(即在J2EE应用程序服务器中)一起工作,那么您应该使用JobStoreCMT - 在这种情况下,Quartz将让应用程序服务器容器管理事务。
最后设置一个DataSource,JDBCJobStore可以从中获取与数据库的连接。 DataSources在Quartz属性中使用几种不同的方法之一进行定义。
一种方法是让Quartz创建和管理DataSource本身 - 通过提供数据库的所有连接信息。
另一种方法是让Quartz使用由Quartz正在运行的应用程序服务器管理的DataSource,通过提供JDBCJobStore DataSource的JNDI名称
要使用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的类版本问题。
示例
配置文件:
#============================================================================ # Configure Main Scheduler Properties #============================================================================ org.quartz.scheduler.instanceName: ArtisanScheduler org.quartz.scheduler.instanceId: AUTO org.quartz.scheduler.skipUpdateCheck: true org.quartz.scheduler.rmi.export: false org.quartz.scheduler.rmi.proxy: false org.quartz.scheduler.wrapJobExecutionInUserTransaction: false #============================================================================ # Configure ThreadPool #============================================================================ org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount: 3 org.quartz.threadPool.threadPriority: 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true #============================================================================ # Configure JobStore # (choose the right driverDelegateClass, for oracle:OracleDelegate ,for mysql:StdJDBCDelegate) #============================================================================ org.quartz.jobStore.misfireThreshold: 60000 org.quartz.jobStore.class : org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass : org.quartz.impl.jdbcjobstore.oracle.OracleDelegate org.quartz.jobStore.dataSource : qzDS org.quartz.jobStore.tablePrefix : QRTZ_ org.quartz.jobStore.useProperties : true org.quartz.jobStore.isClustered: false #============================================================================ # Configure Datasource ORACLE #============================================================================ org.quartz.dataSource.qzDS.connectionProvider.class : com.xgj.quartz.quartzItself.saveInfoInDB.MyPoolingconnectionProvider org.quartz.dataSource.qzDS.driverClassName : oracle.jdbc.driver.OracleDriver org.quartz.dataSource.qzDS.url : jdbc:oracle:thin:@172.25.246.11:1521:testbed org.quartz.dataSource.qzDS.username : cc org.quartz.dataSource.qzDS.password : xxxx
Job
package com.xgj.quartz.quartzItself.saveInfoInDB; import java.text.SimpleDateFormat; import java.util.Date; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; public class MyPersistenceJob implements Job { @Override 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())); } }
数据源连接
package com.xgj.quartz.quartzItself.saveInfoInDB; import java.sql.Connection; import java.sql.SQLException; import org.apache.commons.dbcp.BasicDataSource; import org.quartz.utils.ConnectionProvider; /** * * * @ClassName: MyPoolingconnectionProvider * * @Description: MyPoolingconnectionProvider数据源连接和quartz.properties配置文件。 * * 数据源是自己定义的类,实现了quartz自带的ConnectionProvider类,如果不想使用它, * 你也可以选择其他数据源, * 比如Tomcat的DataSource,Spring的SimpleDriverDataSource等,自行选择. * * DBCP数据源连接池的属性,这里仅仅使用了必须的配置,其他配置也显式设置,也可使用默认值,根据需要执行调整。 * * @author: Mr.Yang * * @date: 2017年10月10日 下午9:49:07 */ public class MyPoolingconnectionProvider implements ConnectionProvider { /** * 使用apache dbcp数据源连接池 */ private BasicDataSource datasource = new BasicDataSource(); private String driverClassName; private String url; private String username; private String password; @Override public Connection getConnection() throws SQLException { System.out.println("getConnection"); return datasource.getConnection(); } @Override public void shutdown() throws SQLException { System.out.println("connection pool shutdown"); datasource.close(); } @Override public void initialize() throws SQLException { try { System.out.println("inti connection"); datasource.setDriverClassName(driverClassName); datasource.setUrl(url); datasource.setUsername(username); datasource.setPassword(password); } catch (Exception e) { e.printStackTrace(); } } public void setDatasource(BasicDataSource datasource) { this.datasource = datasource; } public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; } public void setUrl(String url) { this.url = url; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } }
测试类
package com.xgj.quartz.quartzItself.saveInfoInDB; import java.text.SimpleDateFormat; import java.util.Date; import org.quartz.DateBuilder; import org.quartz.JobBuilder; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SchedulerMetaData; import org.quartz.TriggerBuilder; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.triggers.SimpleTriggerImpl; public class QuartzPersistenceTest { 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/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(MyPersistenceJob.class) .withIdentity("artisanJob", "artisanGroup").build(); // 触发器 重复20+1次 间隔2秒 SimpleTriggerImpl trigger = (SimpleTriggerImpl) TriggerBuilder .newTrigger() .withIdentity("artisanTrigger", "artisanGroup") .startAt(runTime).build(); trigger.setRepeatCount(20); trigger.setRepeatInterval(2000); 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("------- 等待10秒-------------"); try { Thread.sleep(1 * 10000L); } 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(); } } }
通过关闭quartz模拟异常中断的场景
第一次运行日志:
------- 初始化 ---------------------- inti connection INFO StdSchedulerFactory - Using default implementation for ThreadExecutor INFO SimpleThreadPool - Job execution threads will use class loader of thread: main INFO SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl INFO QuartzScheduler - Quartz Scheduler v.2.2.3 created. INFO JobStoreTX - Using thread monitor-based data access locking (synchronization). INFO JobStoreTX - JobStoreTX initialized. INFO QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.2.3) 'ArtisanScheduler' with instanceId 'NON_CLUSTERED' Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally. NOT STARTED. Currently in standby mode. Number of jobs executed: 0 Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 3 threads. Using job-store 'org.quartz.impl.jdbcjobstore.JobStoreTX' - which supports persistence. and is not clustered. INFO StdSchedulerFactory - Quartz scheduler 'ArtisanScheduler' initialized from the specified file : 'quartz/quartz.properties' from the class resource path. INFO StdSchedulerFactory - Quartz scheduler version: 2.2.3 getConnection ------- 初始化完成 ----------- ------- Scheduling Job ------------------- ------- 当前时间:2017-10-12 17:35:53 ----------------- getConnection artisanGroup.artisanJob 开始job运行时间:2017-10-12 17:36:00 getConnection INFO JobStoreTX - Freed 0 triggers from 'acquired' / 'blocked' state. INFO JobStoreTX - Recovering 0 jobs that were in-progress at the time of the last shut-down. INFO JobStoreTX - Recovery complete. INFO JobStoreTX - Removed 0 'complete' triggers. INFO JobStoreTX - Removed 0 stale fired job entries. INFO QuartzScheduler - Scheduler ArtisanScheduler_$_NON_CLUSTERED started. getConnection ------- 开始调度器 Scheduler ----------------- ------- 等待10秒------------- getConnection getConnection getConnection 任务key artisanGroup.artisanJob执行时间:2017-10-12 17:36:00 getConnection getConnection getConnection 任务key artisanGroup.artisanJob执行时间:2017-10-12 17:36:02 getConnection ------- 关闭调度器 模拟异常退出--------------------- INFO QuartzScheduler - Scheduler ArtisanScheduler_$_NON_CLUSTERED shutting down. INFO QuartzScheduler - Scheduler ArtisanScheduler_$_NON_CLUSTERED paused. connection pool shutdown INFO QuartzScheduler - Scheduler ArtisanScheduler_$_NON_CLUSTERED shutdown complete. ------- 异常退出 ----------------- 目前执行了 2 个 jobs.
数据库表qrtz_simple_triggers数据:
将测试类中的下列代码屏蔽,使其正常运行
System.out.println("------- 等待10秒-------------"); try { Thread.sleep(1 * 10000L); } catch (Exception e) { } System.out.println("------- 关闭调度器 模拟异常退出---------------------"); sched.shutdown(true); System.out.println("------- 异常退出 -----------------");
再次运行
qrtz_simple_triggers 中的 times_triggered字段重0开始计算, 执行完成后,该表的数据为空。
总结
简单的10个字概括就是:未执行,插入; 执行过,删除。
示例源码
代码已托管到Github—> https://github.com/yangshangwei/SpringMaster