(一)、JobStore接口
??Quartz中的Scheduler調度器、Job任務、Trigger觸發器在前面都已經介紹了,但是未曾提過這些數據是存放在哪里的。要知道,就算不進行持久化,這些信息也應該有個地方進行存儲的。Quartz提供了兩種不同類型的存儲方式,內存存儲和數據庫存儲。這兩種方式都是基于org.quartz.spi.JobStore接口來實現的。
我們先看一下下面這個圖,這是從Eclipse上截取的:
- org.quartz.spi.JobStore 是任務存儲的頂層接口類
- org.quartz.simpl.RAMJobStore 是內存存儲機制實現類
- org.quartz.impl.jdbcjobstore.JobStoreSupport 是基于JDBC數據庫存儲的抽象類
- org.quartz.impl.jdbcjobstore.JobStoreCMT 是受應用容器管理事務的數據庫存儲實現類
- org.quartz.impl.jdbcjobstore.JobStoreTX 是不受應用容器事務管理的數據庫存儲實現類
??org.quartz.spi.JobStore作為任務存儲的頂層接口類,他定義了很多的接口方法,總共可歸納為四類,調度器類、任務類、觸發器類和之前未提到的Calendar日期這一類,Calendar主要是配合觸發器一起設置一些特殊的觸發時間而使用的。在項目開發中,我們無需調用JobStore實現類中的方法,但是了解還是很有必要的,因為可以讓我們在項目應用中選擇更加適合的存儲類型。如何框架提供的存儲機制不能滿足要求,還可以自定義其他的存儲方式,比如文件系統存儲,如果真這么干,那就需求自己實現JobStore接口,并且實現大約40個接口方法,可以參考RAMJobStore類來看看框架內部具體做了什么再去實現自己的存儲類。
(二)、JobStore接口的幾種實現類
??接下來我們了解一下上圖中提到的幾種存儲方式。
(1)、使用RAMJobStore內存存儲數據
Quartz默認的存儲機制就是使用內存進行存儲的,我們先看一下Quartz的jar包中的默認配置文件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
??主要看org.quartz.jobStore.class這個屬性,屬性值org.quartz.simpl.RAMJobStore就是內存存儲機制的實現類。如果需要使用別的存儲機制,那就將此值替換為別的實現類即可。
??使用內存存儲的優點是任務的存儲和讀取的速度極快,和數據庫持久化相比差別還是非常大的,而且框架搭建簡單,開箱即用。它的缺點是當Quartz程序或應用程序停止了,伴隨在內存中的數據也會被回收,任務等數據就永久丟失了。
??使用內存存儲時,注意配置文件中只需要保留基本的線程池配置和jobStore的實現類等幾個簡單的屬性就行。如果使用了實現類中沒有的屬性,啟動的時候會報錯,當然錯誤提示也很明顯。這里主要想提醒一下之前已經使用了持久化配置,現在想體驗一下內存存儲的朋友們。
(2)、使用數據庫存儲數據
??通過上面的類圖可以看出,JobStoreTX和JobStoreCMT都是JobStoreSupport抽象類的實現類,JobStoreSupport是基于JDBC實現了一些基本的功能的抽象類。如果想要自己實現一套關于JDBC存儲方式,那么可以繼承此抽象類。
我們先看一下Quartz支持哪些數據庫:
·Oracle
·MySQL
·Microsoft SQL Server 2000
·HSQLDB
·PostgreSQL
·DB2
·Cloudscape/Derby
·Pointbase
·Informix
·Firebird
。。。等等,總之兼容JDBC驅動的關系型數據庫都可以。
??了解了哪些數據庫可以使用,接下來就是創建數據庫了,Quartz提供了各種數據庫的腳本,腳本中有創建表和索引的sql,但是沒有創建數據庫的sql,需要自己先創建數據庫,然后執行創建表和索引的腳本。腳本的創建已經在@一、Quartz集成-下載和安裝章節中的第三節講過,這里不再重復講述。
下面介紹一下各個表的含義:
表名 | 含義 |
---|---|
QRTZ_CALENDARS | 以 Blob 類型存儲 Quartz 的 Calendar 信息 |
QRTZ_CRON_TRIGGERS | 存儲CronTrigger觸發器信息,包括Cron表達式和時區等信息 |
QRTZ_FIRED_TRIGGERS | 存儲已觸發的Trigger狀態信息和關聯的Job執行信息 |
QRTZ_PAUSED_TRIGGER_GRPS | 存儲已暫停的Trigger組信息 |
QRTZ_SCHEDULER_STATE | 存儲有關Scheduler的狀態信息 |
QRTZ_LOCKS | 存儲程序鎖信息 |
QRTZ_JOB_DETAILS | 存儲Job的詳細信息 |
QRTZ_JOB_LISTENERS | 存儲Job配置的JobListener信息 |
QRTZ_SIMPLE_TRIGGERS | 存儲SimpleTrigger觸發器信息,包括重復次數,間隔等信息 |
QRTZ_BLOG_TRIGGERS | 存儲Blob類型的Trigger,一般用于自定義觸發器 |
QRTZ_TRIGGER_LISTENERS | 存儲已配置的TriggerListener信息 |
QRTZ_TRIGGERS | 存儲已配置的Trigger的信息 |
??表的前綴默認都是QRTZ_ 開始,我們先了解一下這個表前綴有什么用。假設項目中需要有兩套調度器實例,我們想分別持久化這兩個實例信息,此時就需要兩套上面的表。為了區分表名稱,就在前面加上表前綴。比如:
??org.quartz.jobStore.tablePrefix=QRTZ1_
??org.quartz.jobStore.tablePrefix=QRTZ2_
??QRTZ1_ 和QRTZ2_分別是兩套表的前綴,分別配置在不同的quartz.properties中,然后根據兩個配置文件分別初始化調度實例。
數據庫創建好了,下面先使用JobStoreTX存儲機制,我們接著往下看:
1、JobStoreTX
??TX就是事務的意思,此存儲機制用于Quartz獨立于應用容器的事務管理,如果是Tomcat容器管理的數據源,那我們定義的事務也不會傳播給Quartz框架內部。通俗的講就是不管我們的Service服務本身業務代碼是否執行成功,只要代碼中調用了Quartz API的數據庫操作,那任務狀態就永久持久化了,就算業務代碼拋出運行時異常任務狀態也不會回滾到之前的狀態。
下面介紹一下使用JobStoreTX配置步驟,所有的配置都是在quartz.properties中完成:
- 第一步配置org.quartz.jobStore.class屬性:
org.quartz.jobStore.class= org.quartz.impl.jdbcjobstore.JobStoreTX
- 第二步配置驅動代理,以Mysql為例:
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
下面列出一個可用的數據庫代理類表格,方便大家使用,如果表格中沒有列出你想要的代理類,那就使用標準的 JDBC 代理:org.quartz.impl.jdbcjobstore.StdDriverDelegate
數據庫平臺 | 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 |
Oracle | org.quartz.impl.jdbcjobstore.oracle.OracleDelegate |
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 |
- 第三步配置數據源:
org.quartz.jobStore.dataSource=qzDS
org.quartz.dataSource.qzDS.driver= com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL= jdbc:mysql://localhost:3306/testDB
org.quartz.dataSource.qzDS.user= root
org.quartz.dataSource.qzDS.password= admin
org.quartz.dataSource.qzDS.maxConnection= 20
??這里要注意兩點:
??第一是org.quartz.dataSource.qzDS.URL屬性名末尾的URL字符串必須是大寫,如果寫成org.quartz.dataSource.qzDS.url ,那初始化調度實例時就會報錯。
??第二是注意org.quartz.jobStore.dataSource屬性,這個屬性的意思是給數據源起一個名字。這里屬性值配置的是“qzDS”,你也可以配置成別的任意字符串,比如:“abc”,如果真這么做,那就需要將org.quartz.dataSource.qzDS.driver和其他配置的“qzDS”更換為“abc”,配置:
org.quartz.jobStore.dataSource=abc
org.quartz.dataSource.abc.driver= com.mysql.jdbc.Driver
org.quartz.dataSource.abc.URL= jdbc:mysql://localhost:3306/testDB
org.quartz.dataSource.abc.user= root
org.quartz.dataSource.abc.password= admin
org.quartz.dataSource.abc.maxConnection= 20
??那么Quartz為什么設計要org.quartz.jobStore.dataSource屬性呢?
??這個屬性主要的目的就是在同一個數據庫中需要使用多套Quartz,一般大家只需要一套數據源就可以完成業務工作,除非有一些特別的需求。比如SaaS模式下可以對不同公司的任務調度進行管理等。
下面提供一個拿去就能用的配置文件,改一下數據源、用戶名和密碼即可:
org.quartz.scheduler.instanceName=DefaultQuartzScheduler
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=20
org.quartz.threadPool.threadPriority=5
org.quartz.jobStore.misfireThreshold=60000
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=qzDS
org.quartz.dataSource.qzDS.driver= com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL= jdbc:mysql://localhost:3306/testDB
org.quartz.dataSource.qzDS.user= root
org.quartz.dataSource.qzDS.password= admin
org.quartz.dataSource.qzDS.maxConnection= 20
2、JobStoreCMT
??CMT的全稱是Container Managed Transactions,表示容器管理事務,也就是讓應用容器托管事務。這里假設應用容器是Tomcat,并且項目和Quartz都是使用Tomcat配置的數據源,那么項目和Quartz的代碼中就可以共用同一個事務,不管是業務代碼還是Quartz內部拋出異常,Service服務內的所有數據操作都會回滾到原始狀態。JobStoreCMT和JobStoreTX最大的區別是JobStoreCMT需要配置兩個數據源,一個是受應用容器管理的數據源,還有一個是不受應用容器管理的數據源。
??這里需要想一想為什么需要兩個數據源?
??我個人的理解是不受應用容器管理的數據源用來由Quartz內部進行"增刪改查",假如一個觸發器已失效,那么Quartz框架內部就會自動刪除這個觸發器并提交事務,而無需開發人員的項目代碼來處理,全由Quartz內部管理。
下面介紹一下使用JobStoreCMT配置步驟,所有的配置都是在quartz.properties中完成:
- 第一步配置org.quartz.jobStore.class屬性:
org.quartz.jobStore.class= org.quartz.impl.jdbcjobstore.JobStoreCMT
- 第二步配置驅動代理,以Mysql為例,其它代理類參考上面表格:
org.quartz.jobStore.driverDelegateClass= org.quartz.impl.jdbcjobstore.StdJDBCDelegate
- 第三步配置兩個數據源:
第一個:配置不受應用容器管理的數據源:
org.quartz.jobStore.nonManagedTXDataSource = qzDS
org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL = jdbc:mysql://localhost:3306/testDB
org.quartz.dataSource.qzDS.user = root
org.quartz.dataSource.qzDS.password = admin
org.quartz.dataSource.qzDS.maxConnections = 10
?nonManagedTXDataSource就是非管理事務數據源的意思。
?第二個:配置受應用容器管理的數據源:
org.quartz.dataSource.dataSource=myDS
org.quartz.dataSource.jndiURL = jdbc/mysql
org.quartz.dataSource.myDS.jndiAlwaysLookup = DB_JNDI_ALWAYS_LOOKUP
org.quartz.dataSource.myDS.java.naming.factory.initial = org.apache.naming.java.javaURLContextFactory
org.quartz.dataSource.myDS.java.naming.provider.url = http://localhost:8080
org.quartz.dataSource.myDS.java.naming.security.principal = root
org.quartz.dataSource.myDS.java.naming.security.credentials = admin
??注意:配置之前大家可能需要去了解學習一下JNDI+應用容器(Tomcat等)如何配置數據源,本文就不講述如何配置了。
下面解釋一下受應用容器管理的數據源配置屬性的含義:
- org.quartz.dataSource.NAME.jndiURL
受應用服務器管理的DataSource的JNDI URL - org.quartz.dataSource.NAME.java.naming.factory.initial
JNDI InitialContextFactory的類名稱 - org.quartz.dataSource.NAME.java.naming.provider.url
連接到JNDI的URL - org.quartz.dataSource.NAME.java.naming.security.principal
連接到 JNDI 的用戶名 - org.quartz.dataSource.NAME.java.naming.security.credential
連接到 JNDI 的用戶憑證密碼
(三)、如何選擇使用哪種存儲機制?
1、什么情況下使用RAMJobStore內存存儲方式呢?
??根據開發中的使用經驗,發現有些任務是隨著項目啟動而啟動的,就算項目關閉或系統宕機,那也沒關系,因為項目重新啟動后此任務又會隨之啟動。如果項目中只存在這類任務,那么就可以用內存存儲。隨著項目啟動有幾種常用的實現方式,第一種是通過實現ServletContextListener監聽器接口,然后在接口實現類的contextInitialized()方法中編寫啟動Job的硬編碼;第二種是通過Quartz的XML配置文件啟動任務。
2、什么情況下使用JobStoreTX數據庫存儲方式呢?
??第一篇文章@一、Quartz集成-下載和安裝中的配置就用到了JobStoreTX,那個配置文件是我在實際開發中使用的,使用這種存儲方式的情況很多。使用這種方式需要注意的是,如果在一個Service服務中需要創建一個Job,那么請把創建Job的代碼編寫在服務代碼的最后面,確保業務代碼運行成功并且沒有拋異常再去啟動Job,如果啟動Job失敗的時候請拋出一個運行時異常使業務代碼進行回滾。
例子:
@Transactional
public void demoService(TaskStore taskStore) {
// 先執行插入業務操作
taskStoreService.insert(taskStore);
// 再執行更新業務操作
taskDetailService.update(taskDetail);
// 最后啟動定時任務
QuartzUtils.addJob("testName", DemoJob.class, "0 * * * * * ?");
}
??注意例子中的addJob()方法中捕獲了異常后進行重新封裝再拋出運行時異常的,目的是Quartz內部錯誤時確保業務代碼回滾。
3、什么情況下使用JobStoreCMT數據庫存儲方式呢?
??JobStoreCMT和JobStoreTX的區別前文已經介紹了,在實際開發的過程中我還沒有在項目中使用過此種方式。一般情況下都是使用的JobStoreTX。如果大家的項目中有著嚴格的事務管理,那么建議使用JobStoreCMT存儲方式。