1. 程式人生 > >Quartz時間排程框架的學習(一)

Quartz時間排程框架的學習(一)

文章轉載自:

            http://blog.csdn.net/qwe6112071/article/details/50991563

            https://blog.csdn.net/wenniuwuren/article/details/41483667

            https://blog.csdn.net/qwe6112071/article/details/50989406

Quartz框架需求引入

在現實開發中,我們常常會遇到需要系統在特定時刻完成特定任務的需求,在《spring學習筆記(14)引介增強詳解:定時器例項:無侵入式動態增強類功能》,我們通過引介增強來簡單地模擬實現了一個定時器。它可能只需要我們自己維護一條執行緒就足以實現定時監控。但在實際開發中,我們遇到的需求會複雜很多,可能涉及多點任務排程,需要我們多執行緒併發協作、執行緒池的維護、對執行時間規則進行更細粒度的規劃、執行執行緒現場的保持與恢復等等。如果我們選擇自己來造輪子,可能會遇到許多難題。這時候,引入Quartz任務排程框架會是一個很好的選擇,它: 
1. 允許我們靈活而細粒度地設定任務觸發時間,比如常見的每隔多長時間,或每天特定時刻、特定日子(如節假日),都能靈活地配置這些成相應時間點來觸發我們的任務 
2. 提供了排程環境的持久化機制,可以將任務內容儲存到資料庫中,在完成後刪除記錄,這樣就能避免因系統故障而任務尚未執行且不再執行的問題。 
3. 提供了元件式的偵聽器、各種外掛、執行緒池等。通過偵聽器,我們可以引入我們的事件機制,配合上非同步呼叫,既能為我們的業務處理類解耦,同時還可能提升使用者體驗,關於事件機制的基本概念、解耦特性及其非同步呼叫可移步參考我的另一篇文章

《spring學習筆記(15)趣談spring 事件:實現業務邏輯解耦,非同步呼叫提升使用者體驗 》

例項解析概念

在quartz中,有幾個核心類和介面:Job、JobDetail、Trigger、Calendar、Scheduler。 
下面我們結合例項來分析這些類的角色定位。 
現在我們有一個新聞網站,它有一張任務日誌表,記錄著我們的不同任務,比如每隔三十分鐘要根據文章的閱讀量和評論量來生成我們的最熱文章列表。在每天早晚12點,定時從其他新聞網站扒取一定量新聞,在每週一晚上12點到3點進行論壇封閉維護,而如果遇到節假日則不維護等。在以上例項中:

  1. 生成最熱文章扒取新聞論壇封閉維護都是我們的Job,它定義了我們的需要執行的任務,是一個抽象的介面.
  2. 如果我們要具體到每隔三十分鐘生成最熱文章早晚12點扒取新聞等,在我們的具體任務執行時刻,我們就需要能夠描述Job及其他相關靜態資訊jobDetail,它相當於是我們的Job+具體實現細節
  3. Trigger則描述了Job執行的時間觸發規則,比如每隔三十分鐘早晚12點
  4. 而這裡的Calendar可以看成是一些日曆特定時間點的集合,比如我們這裡遇到節假日則不維護,節假日如國慶節、愚人節等等,都是我們的日曆特定時間點。
  5. Scheduler就是我們的任務日誌表,它是一個容器,記載(容納)了我們前面的工作、觸發時間等內容。

具體用法詳解

通過例項,我們對quartz的核心類有了較清晰的功能定位,根據Quratz的不同版本,這幾個核心類有較大改動,具體的操作不太相同,但是思路是相同的;比如1.+版本jar包中,JobDetail是個類,直接通過構造方法與Job類關聯。SimpleTrigger和CornTrigger是類;在2.+jar包中,JobDetail是個介面,SimpleTrigger和CornTrigger是介面。下面詳細地分析它們的具體用法:

1. Job

Job是一個介面,只有一個void execute(JobExecutionContext jec)方法,JobExecutionContext提供了我們的任務排程上下文資訊,比如,我們可以通過JobExecutionContext獲取job相對應的JobDetail、Trigger等資訊,我們在配置自己的內容時,需要實現此類,並在execute中重寫我們的任務內容。下面是我們的扒取新聞工作例項:

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())+"扒取新聞");
    }
}

2. JobDetail

Quartz在每次執行任務時,都會建立一個Job例項,併為其配置上下文資訊,jobDetail有一個成員屬性JobDataMap,存放了我們Job執行時的具體資訊,在後面我們會詳細提到。 
1. 在1.+版本中,它作為一個類,常用的構造方法有:JobDetail(String name, String group, Class jobClass),我們需要指定job的名稱,組別和實現了Job介面的自定義任務類。例項如JobDetail jobDetail =new JobDetail("job1", "jgroup1", pickNewsJob.class); 
2. 而在2.+版本中,我們則通過一下方法建立JobBuilder.newJob(自定義任務類).withIdentity(任務名稱,組名).build();例項如JobDetail jobDetail = JobBuilder.newJob(pickNewsJob.class).withIdentity(“job1”,”group1”).build();`

3. Scheduler

先講Scheduler,方便後講解Trigger時測試。 
Scheduler作為我們的“任務記錄表”,裡面(可以)配置大量的Trigger和JobDetail,兩者在 Scheduler中擁有各自的組及名稱,組及名稱是Scheduler查詢定位容器中某一物件的依據,Trigger的組及名稱必須唯一,JobDetail的組和名稱也必須唯一(但可以和Trigger的組和名稱相同,因為它們是不同型別的)。Scheduler可以將Trigger繫結到某一JobDetail中,這樣當Trigger觸發時,對應的Job就被執行。一個Job可以對應多個Trigger,但一個Trigger只能對應一個Job。可以通過SchedulerFactory建立一個Scheduler例項。下面是使用Schduler的例項:

SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
//將jobDetail和Trigger註冊到一個scheduler裡,建立起兩者的關聯關係
scheduler.scheduleJob(jobDetail,Trigger);

scheduler.start();//開始任務排程

在一個scheduler被建立後,它處於”STAND-BY”模式,在觸發任何job前需要使用它的start()方法來啟動。同樣的,如果我們想根據我們的業務邏輯來停止定時方案執行,可以使用scheduler.standby()方法

3. Trigger

Trigger描述了Job執行的時間觸發規則,主要有SimpleTrigger和CronTrigger兩個子類。

1. SimpleTrigger

如果嵌入事件機制只觸發一次,或意圖使Job以固定時間間隔觸發,則使用SimpleTrigger較合適,它有多個建構函式,其中一個最複雜的建構函式為: 
SimpleTrigger(String name, String group, String jobName, String jobGroup, Date startTime, Date
endTime, int repeatCount, long repeatInterval)
引數依次為觸發器名稱、觸發器所在組名、工作名、工作所在組名、開始日期、結束日期、重複次數、重複間隔。 
1. 如果我們不需同時設定這麼多屬性,可呼叫其他只有部分引數的構造方法,其他引數也可以通過set方法動態設定。 
2. 這裡需要注意的是,如果到了我們設定的endTime,即時重複次數repeatCount還沒有達到我們預設定的次數。任務也不會再此執行。 
下面是1.+版本的建立例項

//建立一個觸發器,使任務從現在開始、每隔兩秒執行一次,共執行10次
SimpleTrigger simpleTrigger = new SimpleTrigger("triiger1");//至少需要設定名字以標識當前觸發器,否則在呼叫時會報錯
simpleTrigger.setStartTime(new Date());
simpleTrigger.setRepeatInterval(2000);
simpleTrigger.setRepeatCount(10);

下面是2.+版本的建立例項

SimpleTrigger simpleTrigger = TriggerBuilder
                .newTrigger()
                .withIdentity("trigger1")//配置觸發器名稱
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(10, 2))//配置重複次數和間隔時間
                .startNow()//設定從當前開始
                .build();//建立操作

通過TriggerBuilder,我們可以通過方法方便地配置觸發器的各種引數。

2. CronTrigger

通過Cron表示式定義複雜的時間排程方案,具體內容我們在下一篇詳細提到

4. Calendar

在實際的開發中,我們可能需要根據節假日來調整我們的任務排程方案。例項如下:

//第一步:建立節假日類
 // ②四一愚人節    
Calendar foolDay = new GregorianCalendar();    //這裡的Calendar是 java.util.Calendar。根據當前時間所在的預設時區建立一個“日子”
foolDay.add(Calendar.MONTH, 4);    
foolDay.add(Calendar.DATE, 1);    
// ③國慶節    
Calendar nationalDay = new GregorianCalendar();    
nationalDay.add(Calendar.MONTH, 10);    
nationalDay.add(Calendar.DATE, 1);  

//第二步:建立AnnualCalendar,它的作用是排除排除每一年中指定的一天或多天
AnnualCalendar holidays = new AnnualCalendar();
//設定排除日期有兩種方法
// 第一種:排除的日期,如果設定為false則為包含(included)   
holidays.setDayExcluded(foolDay, true);  
holidays.setDayExcluded(nationalDay, true); 
//第二種,建立一個數組。
ArrayList<Calendar> calendars = new ArrayList<Calendar>();
calendars.add(foolDay);
calendars.add(nationalDay);
holidays.setDaysExcluded(calendars);

//第三步:將holidays新增進我們的觸發器
simpleTrigger.setCalendarName("holidays");

//第四步:設定好然後需要在我們的scheduler中註冊
scheduler.addCalendar("holidays",holidays, false,false);,注意這裡的第一個引數為calendarName,需要和觸發器中新增的Calendar名字像對應。

在這裡,除了可以使用AnnualCalendar外,還有CronCalendar(表示式),DailyCalendar(指定的時間範圍內的每一天),HolidayCalendar(排除節假日),MonthlyCalendar(排除月份中的數天),WeeklyCalendar(排除星期中的一天或多天)

至此,我們的核心類基本講解完畢,下面附上我們的完整測試程式碼:

/*********************1.+版本*********************/
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 =new JobDetail("job1", "jgroup1", pickNewsJob.class); 
        SimpleTrigger simpleTrigger = new SimpleTrigger("triiger1");
        simpleTrigger.setStartTime(new Date());
        simpleTrigger.setRepeatInterval(2000);
        simpleTrigger.setRepeatCount(10);
        simpleTrigger.setCalendarName("holidays");

        //設定需排除的特殊假日
        AnnualCalendar holidays = new AnnualCalendar();
        // 四一愚人節
        Calendar foolDay = new GregorianCalendar(); // 這裡的Calendar是 ava.util.Calendar。根據當前時間所在的預設時區建立一個“日子”
        foolDay.add(Calendar.MONTH, 4);
        foolDay.add(Calendar.DATE, 1);
        // 國慶節
        Calendar nationalDay = new GregorianCalendar();
        nationalDay.add(Calendar.MONTH, 10);
        nationalDay.add(Calendar.DATE, 1);
        //排除的日期,如果設定為false則為包含(included)
        holidays.setDayExcluded(foolDay, true);
        holidays.setDayExcluded(nationalDay, true);
        /*方法2:通過陣列設定
        ArrayList<Calendar> calendars = new ArrayList<Calendar>();
        calendars.add(foolDay);
        calendars.add(nationalDay);
        holidays.setDaysExcluded(calendars);*/

        //建立scheduler
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
        scheduler.addCalendar("holidays", holidays, false, false);

        scheduler.scheduleJob(jobDetail, simpleTrigger);
        scheduler.start();

    }
}
/*******************2.+版本***************/
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", "group1").build();
        SimpleTrigger simpleTrigger = TriggerBuilder
                .newTrigger()
                .withIdentity("trigger1")
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(10, 2))
                .startNow()
                .build();

        //設定需排除的特殊假日
        AnnualCalendar holidays = new AnnualCalendar();
        // 四一愚人節
        Calendar foolDay = new GregorianCalendar(); // 這裡的Calendar是 ava.util.Calendar。根據當前時間所在的預設時區建立一個“日子”
        foolDay.add(Calendar.MONTH, 4);
        foolDay.add(Calendar.DATE, 1);
        // 國慶節
        Calendar nationalDay = new GregorianCalendar();
        nationalDay.add(Calendar.MONTH, 10);
        nationalDay.add(Calendar.DATE, 1);
        //排除的日期,如果設定為false則為包含(included)
        holidays.setDayExcluded(foolDay, true);
        holidays.setDayExcluded(nationalDay, true);
        /*方法2:通過陣列設定
        ArrayList<Calendar> calendars = new ArrayList<Calendar>();
        calendars.add(foolDay);
        calendars.add(nationalDay);
        holidays.setDaysExcluded(calendars);*/

        //建立scheduler
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
        scheduler.addCalendar("holidays", holidays, false, false);

        scheduler.scheduleJob(jobDetail, simpleTrigger);
        scheduler.start();//關閉排程器的時候呼叫 schedeler.shutdown(true)

可見,兩個不同版本的主要區別在於JobDetail和Triiger的配置。

此外,除了使用scheduler.scheduleJob(jobDetail, simpleTrigger)來建立jobDetail和simpleTrigger的關聯外,在1.+版本中的配置還可以採用如下所示方式

simpleTrigger.setJobName("job1");//jobName和我們前面jobDetail的的名字一致
simpleTrigger.setJobGroup("jgroup1");//jobGroup和我們之前jobDetail的組名一致
scheduler.addJob(jobDetail, true);//註冊jobDetail,此時jobDetail必須已指定job名和組名,否則會拋異常Trigger's related Job's name cannot be null
scheduler.scheduleJob(simpleTrigger);//註冊triiger必須在註冊jobDetail之後,否則會拋異常Trigger's related Job's name cannot be null

這裡還需要注意的是,如果我們使用scheduler.addCalendar("holidays", holidays, false, false)必須在向scheduler註冊trigger之前scheduler.scheduleJob(simpleTrigger),否則會拋異常:Calendar not found: holidays

而在2.+版本中,我嘗試在建立triiger時用forJob(“job1”, “jgroup1”)來繫結job名和組名

SimpleTrigger simpleTrigger = TriggerBuilder
    .newTrigger()
    .withIdentity("trigger1")
    .forJob("job1", "jgroup1")//在這裡繫結
    .withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(10, 2))
    .startNow()
    .build();

//後面是一樣的 
scheduler.addJob(jobDetail, true); 
scheduler.scheduleJob(simpleTrigger);

在執行時,卻會丟擲異常: Jobs added with no trigger must be durable. 

顯然是繫結失敗了,目前暫未找到解決方法,如果有找到解決方法的朋友,懇請告訴我一下,十分感謝!

第一部分的總結:

大概流程:

    1.編寫我們自己的定時任務類myJob實現job介面,並重寫excute,這就是我們自己的任務

    2.根據myjob建立jobDetail物件

    3.建立時間出發規則物件:觸發器Trigger,並指定觸發任務的規則

    4.建立排程器schedule,傳入jobDetail和Trigger物件,啟動排程器,即可按照指定的事件規則觸發定時任務執行

核心概念:

1、Job表示一個工作,要執行的具體內容。此介面中只有一個方法 void execute(JobExecutionContext context);

Job有一個StatefulJob子介面,代表有狀態的任務,該介面是一個沒有方法的標籤介面,其目的是讓Quartz知道任務的型別,以便採用不同的執行方案,預設實現的job介面是無狀態的任務,實現statefulJob介面的任務是有狀態的任務

無狀態任務在執行時擁有自己的JobDataMap拷貝,對JobDataMap的更改不會影響下次的執行。而有狀態任務共享共享同一個JobDataMap例項,每次任務執行對JobDataMap所做的更改會儲存下來,後面的執行可以看到這個更改,也即每次執行任務後都會對後面的執行發生影響。

正因為這個原因,無狀態的Job可以併發執行,而有狀態的StatefulJob不能併發執行,這意味著如果前次的StatefulJob還沒有執行完畢,下一次的任務將阻塞等待,直到前次任務執行完畢。有狀態任務比無狀態任務需要考慮更多的因素,程式往往擁有更高的複雜度,因此除非必要,應該儘量使用無狀態的Job。

如果Quartz使用了資料庫持久化任務排程資訊,無狀態的JobDataMap僅會在Scheduler註冊任務時保持一次,而有狀態任務對應的JobDataMap在每次執行任務後都會進行儲存。
Trigger自身也可以擁有一個JobDataMap,其關聯的Job可以通過JobExecutionContext.getTrigger().getJobDataMap()獲取Trigger中的JobDataMap。不管是有狀態還是無狀態的任務,在任務執行期間對Trigger的JobDataMap所做的更改都不會進行持久,也即不會對下次的執行產生影響。
2、JobDetailQuartz每次排程Job時, 都重新建立一個Job例項, 所以它不直接接受一個Job的例項,相反它可以接受jobDetail,jobDetail描述Job的實現類及其它相關的靜態資訊,如Job名字、描述、關聯監聽器等資訊),以便執行時通過newInstance()的反射機制例項化Job,jobDetail有一個成員屬性JobDataMap,存放了我們Job執行時的具體資訊;3、是一個類,描述觸發Job執行的時間觸發規則。主要有SimpleTrigger和CronTrigger這兩個子類。當且僅當需排程一次或者以固定時間間隔週期執行排程,SimpleTrigger是最適合的選擇;而CronTrigger則可以通過Cron表示式定義出各種複雜時間規則的排程方案:如工作日週一到週五的15:00~16:00執行排程等;

4. Calendar:org.quartz.Calendar和java.util.Calendar不同, 它是一些日曆特定時間點的集合(可以簡單地將org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一個日曆時間點,無特殊說明後面的Calendar即指org.quartz.Calendar)。 一個Trigger可以和多個Calendar關聯, 以便排除或包含某些時間點;

假設,我們安排每週星期一早上10:00執行任務,但是如果碰到法定的節日,任務則不執行,這時就需要在Trigger觸發機制的基礎上使用Calendar進行定點排除。針對不同時間段型別,Quartz在org.quartz.impl.calendar包下提供了若干個Calendar的實現類,如AnnualCalendar、MonthlyCalendar、WeeklyCalendar分別針對每年、每月和每週進行定義;

5. Scheduler: 代表一個Quartz的獨立執行容器, Trigger和JobDetail可以註冊到Scheduler中, 兩者在Scheduler中擁有各自的組及名稱, 組及名稱是Scheduler查詢定位容器中某一物件的依據, Trigger的組及名稱必須唯一, JobDetail的組和名稱也必須唯一(但可以和Trigger的組和名稱相同,因為它們是不同型別的)。Scheduler定義了多個介面方法, 允許外部通過組及名稱訪問和控制容器中Trigger和JobDetail;

Scheduler可以將Trigger繫結到某一JobDetail中, 這樣當Trigger觸發時, 對應的Job就被執行。一個Job可以對應多個Trigger, 但一個Trigger只能對應一個Job。可以通過SchedulerFactory建立一個Scheduler例項。Scheduler擁有一個SchedulerContext,它類似於ServletContext,儲存著Scheduler上下文資訊,Job和Trigger都可以訪問SchedulerContext內的資訊。SchedulerContext內部通過一個Map,以鍵值對的方式維護這些上下文資料,SchedulerContext為儲存和獲取資料提供了多個put()和getXxx()的方法。可以通過Scheduler. getContext()獲取對應的SchedulerContext例項;

6. ThreadPool: Scheduler使用一個執行緒池作為任務執行的基礎設施,任務通過共享執行緒池中的執行緒提高執行效率。

實現原理:

1、scheduler是一個計劃排程器容器(總部),容器裡面可以盛放眾多的JobDetail和trigger,當容器啟動後,裡面的每個JobDetail都會根據trigger按部就班自動去執行。2、JobDetail是一個可執行的工作,它本身可能是有狀態的。3、Trigger代表一個排程引數的配置,什麼時候去調。4、當JobDetail和Trigger在scheduler容器上註冊後,形成了裝配好的作業(JobDetail和Trigger所組成的一對兒),就可以伴隨容器啟動而排程執行了。5、scheduler是個容器,容器中有一個執行緒池,用來並行排程執行每個作業,這樣可以提高容器效率。

原理圖:

            


記憶體儲存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
  (
    SCHE