1. 程式人生 > >Quartz任務排程(3)儲存與持久化操作配置詳細解析

Quartz任務排程(3)儲存與持久化操作配置詳細解析

記憶體儲存RAMJobStore

Quartz預設使用RAMJobStore,它的優點是速度。因為所有的 Scheduler 資訊都儲存在計算機記憶體中,訪問這些資料隨著電腦而變快。而無須訪問資料庫或IO等操作,但它的缺點是將 Job 和 Trigger 資訊儲存在記憶體中的。因而我們每次重啟程式,Scheduler 的狀態,包括 Job 和 Trigger 資訊都丟失了。
Quartz 的記憶體 Job 儲存的能力是由一個叫做 org.quartz.simple.RAMJobStore 類提供。在我們的quartz-2.x.x.jar包下的org.quartz包下即儲存了我們的預設配置quartz.properties。開啟這個配置檔案,我們會看到如下資訊

# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
#

org.quartz.scheduler.instanceName: DefaultQuartzScheduler
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.threadCount: 10 org.quartz.threadPool.threadPriority: 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true org.quartz.jobStore.misfireThreshold: 60000 org.quartz.jobStore.class: org.quartz
.simpl.RAMJobStore #這裡預設使用RAMJobStore

永續性JobStore

Quartz 提供了兩種型別的永續性 JobStore,為JobStoreTX和JobStoreCMT,其中:
1. JobStoreTX為獨立環境中的永續性儲存,它設計為用於獨立環境中。這裡的 “獨立”,我們是指這樣一個環境,在其中不存在與應用容器的事物整合。這裡並不意味著你不能在一個容器中使用 JobStoreTX,只不過,它不是設計來讓它的事特受容器管理。區別就在於 Quartz 的事物是否要參與到容器的事物中去。
2. JobStoreCMT 為程式容器中的永續性儲存,它設計為當你想要程式容器來為你的 JobStore 管理事物時,並且那些事物要參與到容器管理的事物邊界時使用。它的名字明顯是來源於容器管理的事物(Container Managed Transactions (CMT))。

持久化配置步驟

要將JobDetail等資訊持久化我們的資料庫中,我們可按一下步驟操作:

1. 配置資料庫

在 /docs/dbTables 目錄下存放了幾乎所有資料庫的的SQL指令碼,這裡的 是解壓 Quartz 分發包後的目錄。我們使用常用mysql資料庫,下面是示例sql指令碼程式碼

#
# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
#
# PLEASE consider using mysql with innodb tables to avoid locking issues
#
# In your Quartz properties file, you'll need to set 
# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#

DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;


CREATE TABLE QRTZ_JOB_DETAILS
  (
    SCHED_NAME VARCHAR(80) NOT NULL,
    JOB_NAME  VARCHAR(100) NOT NULL,
    JOB_GROUP VARCHAR(100) NOT NULL,
    DESCRIPTION VARCHAR(100) NULL,
    JOB_CLASS_NAME   VARCHAR(100) 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 NULL,
    PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);

CREATE TABLE QRTZ_TRIGGERS
  (
    SCHED_NAME VARCHAR(80) NOT NULL,
    TRIGGER_NAME VARCHAR(100) NOT NULL,
    TRIGGER_GROUP VARCHAR(100) NOT NULL,
    JOB_NAME  VARCHAR(100) NOT NULL,
    JOB_GROUP VARCHAR(100) NOT NULL,
    DESCRIPTION VARCHAR(100) NULL,
    NEXT_FIRE_TIME BIGINT(13) NULL,
    PREV_FIRE_TIME BIGINT(13) NULL,
    PRIORITY INTEGER NULL,
    TRIGGER_STATE VARCHAR(16) NOT NULL,
    TRIGGER_TYPE VARCHAR(8) NOT NULL,
    START_TIME BIGINT(13) NOT NULL,
    END_TIME BIGINT(13) NULL,
    CALENDAR_NAME VARCHAR(100) NULL,
    MISFIRE_INSTR SMALLINT(2) NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
        REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
);

CREATE TABLE QRTZ_SIMPLE_TRIGGERS
  (
    SCHED_NAME VARCHAR(80) NOT NULL,
    TRIGGER_NAME VARCHAR(100) NOT NULL,
    TRIGGER_GROUP VARCHAR(100) 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),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_CRON_TRIGGERS
  (
    SCHED_NAME VARCHAR(80) NOT NULL,
    TRIGGER_NAME VARCHAR(100) NOT NULL,
    TRIGGER_GROUP VARCHAR(100) NOT NULL,
    CRON_EXPRESSION VARCHAR(100) NOT NULL,
    TIME_ZONE_ID VARCHAR(80),
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_SIMPROP_TRIGGERS
  (          
    SCHED_NAME VARCHAR(80) NOT NULL,
    TRIGGER_NAME VARCHAR(100) NOT NULL,
    TRIGGER_GROUP VARCHAR(100) NOT NULL,
    STR_PROP_1 VARCHAR(120) NULL,
    STR_PROP_2 VARCHAR(120) NULL,
    STR_PROP_3 VARCHAR(120) NULL,
    INT_PROP_1 INT NULL,
    INT_PROP_2 INT NULL,
    LONG_PROP_1 BIGINT NULL,
    LONG_PROP_2 BIGINT NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 VARCHAR(1) NULL,
    BOOL_PROP_2 VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) 
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_BLOB_TRIGGERS
  (
    SCHED_NAME VARCHAR(80) NOT NULL,
    TRIGGER_NAME VARCHAR(100) NOT NULL,
    TRIGGER_GROUP VARCHAR(100) NOT NULL,
    BLOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_CALENDARS
  (
    SCHED_NAME VARCHAR(80) NOT NULL,
    CALENDAR_NAME  VARCHAR(100) NOT NULL,
    CALENDAR BLOB NOT NULL,
    PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);

CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
  (
    SCHED_NAME VARCHAR(80) NOT NULL,
    TRIGGER_GROUP  VARCHAR(100) NOT NULL, 
    PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_FIRED_TRIGGERS
  (
    SCHED_NAME VARCHAR(80) NOT NULL,
    ENTRY_ID VARCHAR(95) NOT NULL,
    TRIGGER_NAME VARCHAR(100) NOT NULL,
    TRIGGER_GROUP VARCHAR(100) NOT NULL,
    INSTANCE_NAME VARCHAR(100) NOT NULL,
    FIRED_TIME BIGINT(13) NOT NULL,
    SCHED_TIME BIGINT(13) NOT NULL,
    PRIORITY INTEGER NOT NULL,
    STATE VARCHAR(16) NOT NULL,
    JOB_NAME VARCHAR(100) NULL,
    JOB_GROUP VARCHAR(100) NULL,
    IS_NONCONCURRENT VARCHAR(1) NULL,
    REQUESTS_RECOVERY VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);

CREATE TABLE QRTZ_SCHEDULER_STATE
  (
    SCHED_NAME VARCHAR(80) NOT NULL,
    INSTANCE_NAME VARCHAR(100) NOT NULL,
    LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
    CHECKIN_INTERVAL BIGINT(13) NOT NULL,
    PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);

CREATE TABLE QRTZ_LOCKS
  (
    SCHED_NAME VARCHAR(80) NOT NULL,
    LOCK_NAME  VARCHAR(40) NOT NULL, 
    PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);

commit;

其中各表的含義如下所示:

表名 描述
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_JOB_LISTENERS 儲存有關已配置的 JobListener 的資訊
QRTZ_SIMPLE_TRIGGERS 儲存簡單的 Trigger,包括重複次數,間隔,以及已觸的次數
QRTZ_BLOG_TRIGGERS Trigger 作為 Blob 型別儲存(用於 Quartz 使用者用 JDBC 建立他們自己定製的 Trigger 型別,JobStore 並不知道如何儲存例項的時候)
QRTZ_TRIGGER_LISTENERS 儲存已配置的 TriggerListener 的資訊
QRTZ_TRIGGERS 儲存已配置的 Trigger 的資訊

2. 使用JobStoreTX

  1. 首先,我們需要在我們的屬性檔案中表明使用JobStoreTX:
    org.quartz.jobStore.class = org.quartz.ompl.jdbcjobstore.JobStoreTX
  2. 然後我們需要配置能理解不同資料庫系統中某一特定方言的驅動代理:
資料庫平臺 Quartz 代理類
Cloudscape/Derby org.quartz.impl.jdbcjobstore.CloudscapeDelegate
DB2 (version 6.x) org.quartz.impl.jdbcjobstore.DB2v6Delegate
DB2 (version 7.x) org.quartz.impl.jdbcjobstore.DB2v7Delegate
DB2 (version 8.x) org.quartz.impl.jdbcjobstore.DB2v8Delegate
HSQLDB org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
MS SQL Server org.quartz.impl.jdbcjobstore.MSSQLDelegate
Pointbase org.quartz.impl.jdbcjobstore.PointbaseDelegate
PostgreSQL org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
(WebLogic JDBC Driver) org.quartz.impl.jdbcjobstore.WebLogicDelegate
(WebLogic 8.1 with Oracle) org.quartz.impl.jdbcjobstore.oracle.weblogic.WebLogicOracleDelegate
Oracle org.quartz.impl.jdbcjobstore.oracle.OracleDelegate

如果我們的資料庫平臺沒在上面列出,那麼最好的選擇就是,直接使用標準的 JDBC 代理 org.quartz.impl.jdbcjobstore.StdDriverDelegate 就能正常的工作。

  1. 以下是一些相關常用的配置屬性及其說明:
屬性 預設值 描述
org.quartz.jobStore.dataSource 用於 quartz.properties 中資料來源的名稱
org.quartz.jobStore.tablePrefix QRTZ_ 指定用於 Scheduler 的一套資料庫表名的字首。假如有不同的字首,Scheduler 就能在同一資料庫中使用不同的表。
org.quartz.jobStore.userProperties False “use properties” 標記指示著永續性 JobStore 所有在 JobDataMap 中的值都是字串,因此能以 名-值 對的形式儲存,而不用讓更復雜的物件以序列化的形式存入 BLOB 列中。這樣會更方便,因為讓你避免了發生於序列化你的非字串的類到 BLOB 時的有關類版本的問題。
org.quartz.jobStore.misfireThreshold 60000 在 Trigger 被認為是錯過觸發之前,Scheduler 還容許 Trigger 通過它的下次觸發時間的毫秒數。預設值(假如你未在配置中存在這一屬性條目) 是 60000(60 秒)。這個不僅限於 JDBC-JobStore;它也可作為 RAMJobStore 的引數
org.quartz.jobStore.isClustered False 設定為 true 開啟叢集特性。如果你有多個 Quartz 例項在用同一套資料庫時,這個屬性就必須設定為 true。
org.quartz.jobStore.clusterCheckinInterval 15000 設定一個頻度(毫秒),用於例項報告給叢集中的其他例項。這會影響到偵測失敗例項的敏捷度。它只用於設定了 isClustered 為 true 的時候。
org.quartz.jobStore.maxMisfiresToHandleAtATime 20 這是 JobStore 能處理的錯過觸發的 Trigger 的最大數量。處理太多(超過兩打) 很快會導致資料庫表被鎖定夠長的時間,這樣就妨礙了觸發別的(還未錯過觸發) trigger 執行的效能。
org.quartz.jobStore.dontSetAutoCommitFalse False 設定這個引數為 true 會告訴 Quartz 從資料來源獲取的連線後不要呼叫它的 setAutoCommit(false) 方法。這在少些情況下是有幫助的,比如假如你有這樣一個驅動,它會抱怨本來就是關閉的又來呼叫這個方法。這個屬性預設值是 false,因為大多數的驅動都要求呼叫 setAutoCommit(false)。
org.quartz.jobStore.selectWithLockSQL SELECT * FROM {0}LOCKS WHERE LOCK_NAME = ? FOR UPDATE 這必須是一個從 LOCKS 表查詢一行並對這行記錄加鎖的 SQL 語句。假如未設定,預設值就是 SELECT * FROM {0}LOCKS WHERE LOCK_NAME = ? FOR UPDATE,這能在大部分資料庫上工作。{0} 會在執行期間被前面你配置的 TABLE_PREFIX 所替換。
org.quartz.jobStore.txIsolationLevelSerializable False 值為 true 時告知 Quartz(當使用 JobStoreTX 或 CMT) 呼叫 JDBC 連線的 setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE) 方法。這有助於阻止某些資料庫在高負載和長時間事物時鎖的超時。

4. 我們還需要配置Datasource 屬性

屬性 必須 說明
org.quartz.dataSource.NAME.driver JDBC 驅動類的全限名
org.quartz.dataSource.NAME.URL 連線到你的資料庫的 URL(主機,埠等)
org.quartz.dataSource.NAME.user 用於連線你的資料庫的使用者名稱
org.quartz.dataSource.NAME.password 用於連線你的資料庫的密碼
org.quartz.dataSource.NAME.maxConnections DataSource 在連線接中建立的最大連線數
org.quartz.dataSource.NAME.validationQuary 一個可選的 SQL 查詢字串,DataSource 用它來偵測並替換失敗/斷開的連線。例如,Oracle 使用者可選用 select table_name from user_tables,這個查詢應當永遠不會失敗,除非直的就是連線不上了。

下面是我們的一個quartz.properties屬性檔案配置例項:

org.quartz.scheduler.instanceName = MyScheduler
org.quartz.threadPool.threadCount = 3
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/quartz?characterEncoding=utf-8
org.quartz.dataSource.myDS.user = root
org.quartz.dataSource.myDS.password = root
org.quartz.dataSource.myDS.maxConnections =5

配置好quartz.properties屬性檔案後,我們只要**將它放在類路徑下,然後執行我們的程式,即可覆蓋在quartz.jar包中預設的配置檔案

3. 測試

編寫我們的測試檔案,我們的測試環境是在quartz-2.2.2版本下進行的。下面的測試用例引用了上篇文章 ,關於Quartz的快速入門配置可移步參考這篇文章。

public class pickNewsJob implements Job {

    @Override
    public void execute(JobExecutionContext jec) throws JobExecutionException {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        System.out.println("在"+sdf.format(new Date())+"扒取新聞");
    }

    public static void main(String args[]) throws SchedulerException {
        JobDetail jobDetail = JobBuilder.newJob(pickNewsJob.class)
                .withIdentity("job1", "jgroup1").build();
        SimpleTrigger simpleTrigger = TriggerBuilder
                .newTrigger()
                .withIdentity("trigger1")
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(10, 2))
                .startNow()
                .build();

        //建立scheduler
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
        scheduler.scheduleJob(jobDetail, simpleTrigger);
        scheduler.start();
    }
}

執行測試方法,能看到控制檯列印如下日誌資訊,關注紅色部分,更注意其中的粗體部分,是我們quartz呼叫資料庫的一些資訊:

INFO : org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
INFO : org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.2.2 created.
INFO : org.quartz.impl.jdbcjobstore.JobStoreTX - Using thread monitor-based data access locking (synchronization).
INFO : org.quartz.impl.jdbcjobstore.JobStoreTX - JobStoreTX initialized.
INFO : org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.2.2) ‘MyScheduler’ 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 : org.quartz.impl.StdSchedulerFactory - Quartz scheduler ‘MyScheduler’ initialized from default resource file in Quartz package: ‘quartz.properties’
INFO : org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.2.2
INFO : 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 -> z8kfsx9f1dp34iubvoy4d|7662953a, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> z8kfsx9f1dp34iubvoy4d|7662953a, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/quartz?characterEncoding=utf-8, lastAcquisitionFailureDefaultUser -> null, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 5, maxStatements -> 0, maxStatementsPerConnection -> 120, minPoolSize -> 1, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> null, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false ]
INFO : org.quartz.impl.jdbcjobstore.JobStoreTX - Freed 0 triggers from ‘acquired’ / ‘blocked’ state.
INFO : org.quartz.impl.jdbcjobstore.JobStoreTX - Recovering 0 jobs that were in-progress at the time of the last shut-down.這裡代表在我們任務開始時,先從資料庫查詢舊記錄,這些舊記錄是之前由於程式中斷等原因未能正常執行的,於是先Recovery回來並執行
INFO : org.quartz.impl.jdbcjobstore.JobStoreTX - Recovery complete.
INFO : org.quartz.impl.jdbcjobstore.JobStoreTX - Removed 0 ‘complete’ triggers.
INFO : org.quartz.impl.jdbcjobstore.JobStoreTX - Removed 0 stale fired job entries.

INFO : org.quartz.core.QuartzScheduler - Scheduler MyScheduler_$_NON_CLUSTERED started.
在21:28:12扒取新聞
在21:28:13扒取新聞
在21:28:15扒取新聞
在21:28:17扒取新聞
….

4. 拓展測試

我們再次執行測試方法,然後馬上中斷程式,查詢我們資料庫,會看到如下內容:
SELECT * FROM QRTZ_SIMPLE_TRIGGERS;
+————-+————–+—————+————–+—————–+—————–+
| SCHED_NAME | TRIGGER_NAME | TRIGGER_GROUP | REPEAT_COUNT | REPEAT_INTERVAL | TIMES_TRIGGERED |
+————-+————–+—————+————–+—————–+—————–+
| MyScheduler | trigger1 | DEFAULT | 9 | 2000 | 1 |
+————-+————–+—————+————–+—————–+—————–+
1 row in set (0.00 sec)

然後我們再執行程式,發現報錯了。
org.quartz.ObjectAlreadyExistsException: Unable to store Job : ‘jgroup1.job1’, because one already exists with this identification.
一般的,在我們的任務排程前,會先將相關的任務持久化到資料庫中,然後呼叫完在刪除記錄,這裡在程式開始試圖將任務資訊持久化到資料庫時,顯然和(因為我們之前中斷操作導致)資料庫中存在的記錄起了衝突。

5. 恢復異常中斷的任務

這個時候,我們可以選擇修改我們的job名和組名和triiger名,然後再執行我們的程式。檢視控制檯列印的資訊部分展示如下:

INFO : org.quartz.impl.jdbcjobstore.JobStoreTX - Freed 1 triggers from ‘acquired’ / ‘blocked’ state.
INFO : org.quartz.impl.jdbcjobstore.JobStoreTX - Handling 1 trigger(s) that missed their scheduled fire-time.這裡我們開始處理上一次異常未完成的儲存在資料庫中的任務記錄
INFO : org.quartz.impl.jdbcjobstore.JobStoreTX - Recovering 0 jobs that were in-progress at the time of the last shut-down.
INFO : org.quartz.impl.jdbcjobstore.JobStoreTX - Recovery complete.
INFO : org.quartz.impl.jdbcjobstore.JobStoreTX - Removed 0 ‘complete’ triggers.
INFO : org.quartz.impl.jdbcjobstore.JobStoreTX - Removed 1 stale fired job entries.
INFO : org.quartz.core.QuartzScheduler - Scheduler MyScheduler_$_NON_CLUSTERED started.
在21:42:13扒取新聞
在21:42:13扒取新聞
在21:42:14扒取新聞
在21:42:15扒取新聞
在21:42:16扒取新聞
在21:42:17扒取新聞
在21:42:18扒取新聞
在21:42:19扒取新聞
在21:42:20扒取新聞
在21:42:21扒取新聞
在21:42:22扒取新聞
在21:42:23扒取新聞
在21:42:24扒取新聞
在21:42:25扒取新聞
在21:42:26扒取新聞
在21:42:27扒取新聞
在21:42:28扒取新聞
在21:42:29扒取新聞
在21:42:30扒取新聞
我們會發現,“扒取新聞”一句的資訊列印次數超過十次,但我們在任務排程中設定了列印十次,說明它恢復了上次的任務排程。
而如果我們不想執行新的任務,只想純粹地恢復之前異常中斷任務,我們可以採用如下方法:

SchedulerFactory schedulerFactory = new StdSchedulerFactory();
    Scheduler scheduler = schedulerFactory.getScheduler();
    // ①獲取排程器中所有的觸發器組
    List<String> triggerGroups = scheduler.getTriggerGroupNames();
    // ②重新恢復在tgroup1組中,名為trigger1觸發器的執行
    for (int i = 0; i < triggerGroups.size(); i++) {//這裡使用了兩次遍歷,針對每一組觸發器裡的每一個觸發器名,和每一個觸發組名進行逐次匹配
        List<String> triggers = scheduler.getTriggerGroupNames();
        for (int j = 0; j < triggers.size(); j++) {
            Trigger tg = scheduler.getTrigger(new TriggerKey(triggers
                    .get(j), triggerGroups.get(i)));
            // ②-1:根據名稱判斷
            if (tg instanceof SimpleTrigger
                    && tg.getDescription().equals("jgroup1.DEFAULT")) {//由於我們之前測試沒有設定觸發器所在組,所以預設為DEFAULT
                // ②-1:恢復執行
                scheduler.resumeJob(new JobKey(triggers.get(j),
                        triggerGroups.get(i)));
            }
        }
    }
    scheduler.start();
}

呼叫此方法,我們在資料庫中異常中斷任務記錄就會被讀取執行,然後被刪除掉。