Sprinboot整合Quartz實現定時任務調度管理

Sprinboot整合Quartz實現定時任務調度管理

版本說明:

springboot版本:2.0.0.RELEASE

quartz版本:2.3.0

github地址:https://github.com/shirukai/quartz-demo.git

Quartz官網:http://www.quartz-scheduler.org/

Quartz是一款開源的定時任務調度框架,本文主要記錄一下在工作中使用springboot整合quartz實現定時任務調度管理的用例。內容主要有:springboot整合quartz相關配置、實現基於simpleTrigger的定時任務、實現基於cronTrigger的定時任務。

1 springboot整合Quartz相關配置

在之前,先創建一個springboot項目。

1.1 引入依賴

springboot整合quartz需要依賴兩個包,quartz-jobs和spring-boot-starter-quartz下面我們在pom.xml文件里加入我們所需要的依賴包

        <!--quartz-->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>

1.2未整合前使用Quartz

參考官網:http://www.quartz-scheduler.org/documentation/quartz-2.2.x/quick-start.html

在整合spring和quartz之前,我們來看一下,如何以普通的方式使用Quartz。

1.2.1 創建可調度Job

在項目中,創建一個job包用來存放我們使用Quartz調度的job。然後我們創建一個HelloJob.java類,來寫我們的Job裏的邏輯。HelloJob類需要繼承org.quartz.Job接口並實現接口裏的execute方法,這裏我們只是簡單的輸出了一句話。代碼如下:

package com.example.quartz.job;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

/**
 * Created by shirukai on 2018/9/6
 */
public class HelloJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("這裏可以執行我們的業務邏輯");
    }
}

1.2.2 使用Quartz調度HelloJob

如上我們已經創建了一個HelloJob類,現在我們要寫一個main方法,使用Quartz對HelloJob進行定時調度。

實現步驟如下:

第一步:使用StdSchedulerFactory工廠創建一個Scheduler實例

第二步:創建一個JobDetail並綁定HelloJob,設置jobName和group

第三步:創建一個Trigger,用以設置定時任務的時間、週期等屬性

第四步:將JobDetail和Trigger傳出Scheduler進行調度

代碼如下:

package com.example.quartz;

import com.example.quartz.job.HelloJob;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;

/**
 * Created by shirukai on 2018/9/6
 */
public class QuartzTest {
    public static void main(String[] args) throws Exception {
        //從工廠創建scheduler實例
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        //開啓scheduler
        scheduler.start();
        //定義一個job,並綁定我們的HelloJob
        JobDetail jobDetail = newJob(HelloJob.class)
                .withIdentity("job1", "group1")
                .build();
        //定義一個simple trigger,設置重複次數爲10次,週期爲2秒
        Trigger trigger = newTrigger()
                .withIdentity("job1", "group1")
                .startNow()
                .withSchedule(simpleSchedule().withIntervalInSeconds(1).withRepeatCount(10)).build();
        //使用scheduler進行job調度
        scheduler.scheduleJob(jobDetail, trigger);
    }
}

執行上述main方法,效果如下;

1.2.3 在HelloJob中使用Spring IOC容器

下面我們改寫HelloJob,看看我們的定時任務能不能調用我們註冊到Spring中的業務。

首先創建一個service包,用於存放我們spring中業務邏輯,並創建一個QuartzService類。

package com.example.quartz.service;

import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.springframework.stereotype.Service;

/**
 * Created by shirukai on 2018/9/6
 */
@Service
public class QuartzService {
    public void printJobInfo(JobExecutionContext context) {
        //從上下文中獲取JobDetail
        JobDetail jobDetail = context.getJobDetail();
        String jobName = jobDetail.getKey().getName();
        String group = jobDetail.getKey().getGroup();
        System.out.println("Schedule job name is:" + jobName);
        System.out.println("Schedule job group is:" + group);
    }
}

修改HelloJob類

package com.example.quartz.job;

import com.example.quartz.service.QuartzService;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Created by shirukai on 2018/9/6
 */
public class HelloJob implements Job {
    @Autowired
    QuartzService quartzService;

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("這裏可以執行我們的業務邏輯");
        quartzService.printJobInfo(context);
    }
}

然後執行main方法,發現報錯,這是因爲我們使用了spring的IOC容器,所以我們要啓動spring才能進行測試。否則我們獲取不到我們注入到spring裏的bean,會得到空指針異常。

創建上面main方法的junit單元測試類,並啓用spring如下所示:

package com.example.quartz;

import com.example.quartz.job.HelloJob;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;

/**
 * Created by shirukai on 2018/9/6
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class QuartzTestTest {

    @Test
    public void main() throws Exception {
        //從工廠創建scheduler實例
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        //開啓scheduler
        scheduler.start();
        //定義一個job,並綁定我們的HelloJob
        JobDetail jobDetail = newJob(HelloJob.class)
                .withIdentity("job1", "group1")
                .build();
        //定義一個simple trigger,設置重複次數爲10次,週期爲2秒
        Trigger trigger = newTrigger()
                .withIdentity("job1", "group1")
                .startNow()
                .withSchedule(simpleSchedule().withIntervalInSeconds(1).withRepeatCount(10)).build();
        //使用scheduler進行job調度
        scheduler.scheduleJob(jobDetail, trigger);
    }
}

執行單元測試之後,發現仍然報空指針異常,這是爲什麼呢,因爲我們沒有與spring整合,我們的job裏是沒法注入spring ioc管理的bean的,也就是說,沒法在job裏調用spring裏的業務邏輯。所以接下來我們來看一下spring如何整合Quartz。

1.3 Springboot整合Quartz

在項目目錄下創建一個conf包用來存放我們Quartz的相關配置。

然後創建一個JobFactory類,用於將JobFactory注入到spring裏。如下所示:

package com.example.quartz.conf;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;

/**
 * Created by shirukai on 2018/9/4
 */
@Component
public class JobFactory extends AdaptableJobFactory {
    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        //調用父類的方法
        Object jobInstance = super.createJobInstance(bundle);
        //進行注入
        capableBeanFactory.autowireBean(jobInstance);
        return jobInstance;
    }
}

在創建一個QuartzConfig類,用於注入Scheduler相關的Bean

package com.example.quartz.conf;

import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;

/**
 * Created by shirukai on 2018/9/4
 */
@Configuration
public class QuartzConfig {
    @Autowired
    private JobFactory jobFactory;

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setOverwriteExistingJobs(true);
        schedulerFactoryBean.setJobFactory(jobFactory);
        return schedulerFactoryBean;
    }


    // 創建schedule
    @Bean(name = "scheduler")
    public Scheduler scheduler() throws IOException {
        return schedulerFactoryBean().getScheduler();
    }
}

這時我們已經將Quartz與Springboot簡單的整合到一起,下面我們再次修改一下單元測試類裏的方法,不再使用工廠類去創建Scheduler實例,而是通過註解從spring的ioc容器裏拿到對應的實例,代碼如下:

package com.example.quartz;

import com.example.quartz.job.HelloJob;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;

/**
 * Created by shirukai on 2018/9/6
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class QuartzTestTest {
    @Autowired
    Scheduler scheduler;
    @Test
    public void main() throws Exception {
        //開啓scheduler
        scheduler.start();
        //定義一個job,並綁定我們的HelloJob
        JobDetail jobDetail = newJob(HelloJob.class)
                .withIdentity("job1", "group1")
                .build();
        //定義一個simple trigger,設置重複次數爲10次,週期爲2秒
        Trigger trigger = newTrigger()
                .withIdentity("job1", "group1")
                .startNow()
                .withSchedule(simpleSchedule().withIntervalInSeconds(1).withRepeatCount(10)).build();
        //使用scheduler進行job調度
        scheduler.scheduleJob(jobDetail, trigger);
    }
}

運行測試類,成功執行。效果如下:

1.4 自定義配置文件與持久化

這一小節主要記錄一下Springboot與Quartz的深度整合,一個是自定義Quartz的配置文件、另一個是Quartz定時任務的持久化。

1.4.1 自定義Quartz的配置文件

在項目resources目錄下創建一個quartz.properties配置文件,內容如下:

#使用自己的配置文件
org.quartz.jobStore.useProperties:true

#默認或是自己改名字都行
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
#如果使用集羣,instanceId必須唯一,設置成AUTO
org.quartz.scheduler.instanceId = AUTO


org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true

在上面我們創建的QuartzConfig類中注入我們的配置文件

    @Bean
    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("quartz.properties"));
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setOverwriteExistingJobs(true);
        schedulerFactoryBean.setQuartzProperties(quartzProperties());
        schedulerFactoryBean.setJobFactory(jobFactory);
        return schedulerFactoryBean;
    }

這樣我們就可以使用自定義的配置文件了。

1.4.2 Quartz定時任務的持久化

默認情況下,Quartz是將我們的定時任務的記錄保存到內存裏,等我們再次啓動項目的時候,我們之前設置的定時任務都會被清空,無法持久化。當然Quartz可以將記錄持久化到數據庫中,下面將從自定義DataSource持久化數據和使用Springboot的DataSource兩方面來持久化Quartz的數據。

1.4.2.1 自定義DataSource

首先在配置文件中添加如下內容,用以配置數據庫相關信息:

#存儲方式使用JobStoreTX,也就是數據庫
org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#是否使用集羣(如果項目只部署到 一臺服務器,就不用了)
org.quartz.jobStore.isClustered = false
org.quartz.jobStore.clusterCheckinInterval=20000
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.dataSource = myDS

#配置數據源
#數據庫中quartz表的表名前綴

org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/springboot?characterEncoding=utf-8
org.quartz.dataSource.myDS.user = root
org.quartz.dataSource.myDS.password = hollysys
org.quartz.dataSource.myDS.maxConnections = 5

這樣配置之後我們就可以將Quartz數據持久化到我們指定的數據庫了,但是僅僅是這樣操作是不行的,回報如下錯誤:

從錯誤信息可以看出,與Quartz相關的表不存在。我們需要創建相應的表,建表腳本在官網都可以download。

官網地址:http://www.quartz-scheduler.org/downloads/

到官網下載源碼,然後在源碼quartz-2.3.0/docs/dbTables目錄下可以找到所有數據庫的建表語句,

這裏提供一下2.3.0版mysql innoDB的建表腳本:

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(120) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) 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))
ENGINE=InnoDB;

CREATE TABLE QRTZ_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) 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(200) 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))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) 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))
ENGINE=InnoDB;

CREATE TABLE QRTZ_CRON_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CRON_EXPRESSION VARCHAR(120) 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))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SIMPROP_TRIGGERS
  (          
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) 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))
ENGINE=InnoDB;

CREATE TABLE QRTZ_BLOB_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_CALENDARS (
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))
ENGINE=InnoDB;

CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_FIRED_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
INSTANCE_NAME VARCHAR(200) 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(200) NULL,
JOB_GROUP VARCHAR(200) NULL,
IS_NONCONCURRENT VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SCHEDULER_STATE (
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))
ENGINE=InnoDB;

CREATE TABLE QRTZ_LOCKS (
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME))
ENGINE=InnoDB;

CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);

CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);
CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);

CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);
CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);

commit; 

手動執行sql建表腳本,將所需要的表創建到數據庫裏,關於如何自動初始化建表腳本,後面將會補充到。

1.4.2.2 使用Springboot的DataSource

除了使用我們在配置文件中指定的數據源外,我們話可以使用springboot項目中配置的數據源。

首先需要註釋掉配置文件中與數據源相關的配置,如下所示:

#存儲方式使用JobStoreTX,也就是數據庫
#org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
#org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
##是否使用集羣(如果項目只部署到 一臺服務器,就不用了)
#org.quartz.jobStore.isClustered = false
#org.quartz.jobStore.clusterCheckinInterval=20000
#org.quartz.jobStore.tablePrefix = QRTZ_
#org.quartz.jobStore.dataSource = myDS
#
##配置數據源
##數據庫中quartz表的表名前綴
#
#org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver
#org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/springboot?characterEncoding=utf-8
#org.quartz.dataSource.myDS.user = root
#org.quartz.dataSource.myDS.password = hollysys
#org.quartz.dataSource.myDS.maxConnections = 5

然後再QuartzConfig類中設置我們的數據源,分爲兩步;

第一步 從spring的ioc容器中獲取datasource

    @Autowired
    DataSource dataSource;

第二步 將獲取到的datasource設置到SchedulerFactoryBean裏

        schedulerFactoryBean.setDataSource(dataSource);

這樣我們就可以使用項目中的datasource了。

2 實現基於simpleTrigger的定時任務

先講一下我們將Quartz與Springboot整合實現定時任務管理的實現思路:

  1. 使用自己的表來保存定時任務相關信息
  2. 封裝Quartz相關操作提供基於simpleTrigger和cronTrigger的定時任務設置接口
  3. 對外提供相關操作的API

2.1 創建Schedule表

利用springboot創建Schedule表,用以來保存我們定時任務相關的信息。

2.1.1 創建ScheduleStatusEnum.java枚舉類

在項目entity包下創建定時任務狀態枚舉類,用來映射定時任務的狀態

package com.example.quartz.entity;

/**
 * Created by shirukai on 2018/9/4
 */
public enum ScheduleStatusEnum {
    ACTIVATED(1, "已激活"),
    INACTIVATED(0, "未激活");
    private int state;
    private String stateInfo;

    ScheduleStatusEnum(int state, String stateInfo) {
        this.state = state;
        this.stateInfo = stateInfo;
    }

    public int getState() {
        return state;
    }

    public String getStateInfo() {
        return stateInfo;
    }
}

2.1.2 創建Schedule.java的實體類

在項目entity包下創建Sechedule.java實體類

package com.example.quartz.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

/**
 * Created by shirukai on 2018/9/3
 */
@Entity
public class Schedule implements Serializable {
    @Id
    private String id;
    private String triggerInfo;
    @Enumerated(EnumType.STRING)
    private ScheduleStatusEnum status;
    private String groupName; 
    private String jobName;

    private int record;//運行記錄
    @Temporal(TemporalType.TIMESTAMP)
    @Column(updatable = false)
    @CreationTimestamp
    private Date createdTimestamp;
    @JsonIgnore
    @Temporal(TemporalType.TIMESTAMP)
    @Column(insertable = false)
    @UpdateTimestamp
    private Date updatedTimestamp;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTriggerInfo() {
        return triggerInfo;
    }

    public void setTriggerInfo(String triggerInfo) {
        this.triggerInfo = triggerInfo;
    }

    public ScheduleStatusEnum getStatus() {
        return status;
    }

    public void setStatus(ScheduleStatusEnum status) {
        this.status = status;
    }

    public String getGroupName() {
        return groupName;
    }

    public void setGroupName(String groupName) {
        this.groupName = groupName;
    }

    public String getJobName() {
        return jobName;
    }

    public void setJobName(String jobName) {
        this.jobName = jobName;
    }

    public int getRecord() {
        return record;
    }

    public void setRecord(int record) {
        this.record = record;
    }

    public Date getCreatedTimestamp() {
        return createdTimestamp;
    }

    public void setCreatedTimestamp(Date createdTimestamp) {
        this.createdTimestamp = createdTimestamp;
    }

    public Date getUpdatedTimestamp() {
        return updatedTimestamp;
    }

    public void setUpdatedTimestamp(Date updatedTimestamp) {
        this.updatedTimestamp = updatedTimestamp;
    }
}

2.1.3 創建 ScheduleRepository.java

在項目repository包下創建ScheduleRepository.java,用以使用jpa對數據庫表進行相關的操作。

package com.example.quartz.repository;

import com.example.quartz.entity.Schedule;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * Created by shirukai on 2018/9/4
 */
public interface ScheduleRepository extends JpaRepository<Schedule, String> {
    Schedule findScheduleByJobNameAndGroupName(String jobName, String groupName);

    Schedule findScheduleById(String scheduleId);
}

至此我們Schedule表相關的操作就已經完成了,啓動項目後,我們的表會被自動創建。

2.2 基於SimpleTrigger封裝Quartz相關操作

2.2.1 創建SimpleScheduleDTO.java實體類

創建DTO類是爲了方便我們對象之間的數據傳輸,格式如下所示;

{
    "startTime":0,
    "repeatCount": 100,
    "period": {
        "time": "5",
        "unit": "minutes"
    },
    "endTime":0
}

在項目dto包想創建ScheduleDTO.java實體類

package com.example.quartz.dto;

/**
 * Created by shirukai on 2018/9/7
 */
public class ScheduleDTO {
    private String jobName;
    private String group;
    private long startTime;
    private long endTime;

    public String getJobName() {
        return jobName;
    }

    public void setJobName(String jobName) {
        this.jobName = jobName;
    }

    public String getGroup() {
        return group;
    }

    public void setGroup(String group) {
        this.group = group;
    }

    public long getStartTime() {
        return startTime;
    }

    public void setStartTime(long startTime) {
        this.startTime = startTime;
    }

    public long getEndTime() {
        return endTime;
    }

    public void setEndTime(long endTime) {
        this.endTime = endTime;
    }
}

同目錄下創建Period類映射JSON中的period字段

package com.example.quartz.dto;

/**
 * Created by shirukai on 2018/9/7
 */
public class Period {

    private long time;
    private String unit;

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public String getUnit() {
        return unit;
    }

    public void setUnit(String unit) {
        this.unit = unit;
    }
}

同目錄下創建SimpleScheduleDTO類繼承上面的ScheduleDTO類

package com.example.quartz.dto;

/**
 * Created by shirukai on 2018/9/7
 */
public class SimpleScheduleDTO extends ScheduleDTO {
    private int repeatCount;
    private Period period;

    public int getRepeatCount() {
        return repeatCount;
    }

    public void setRepeatCount(int repeatCount) {
        this.repeatCount = repeatCount;
    }


    public Period getPeriod() {
        return period;
    }

    public void setPeriod(Period period) {
        this.period = period;
    }
}

2.2.2 創建ScheduleManager

創建定時任務管理器ScheduleManager用以封裝Quartz相關的操作。

2.2.2.1 在項目manager包下創建ScheduleManager.java類

創建ScheduleManager類,從spring ioc中注入Quartz的Scheduler,並將ScheduleManager類使用@Component註解註冊到Spring裏。

package com.example.quartz.manager;

import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Created by shirukai on 2018/9/7
 * 定時任務管理器
 */
@Component
public class ScheduleManager {
    @Autowired
    Scheduler scheduler;
    //todo
}
2.2.2.2 構建SimpleScheduleBuilder

根據週期參數

"period": {
        "time": "5",
        "unit": "minutes"
    },

構建SimpleScheduleBuilder。如參數表示每5分鐘爲一個週期,所以我們編寫getSimpleScheduleBuilder方法,傳入如上的參數,構建相應的SimpleSchedulerBuilder,代碼如下

    /**
     * 構建 SimpleScheduleBuilder
     *
     * @param period 週期參數
     * @return SimpleScheduleBuilder
     */
    private SimpleScheduleBuilder getSimpeScheduleBuilder(Period period, int repeatCount) {
        SimpleScheduleBuilder ssb = SimpleScheduleBuilder.simpleSchedule();
        String unit = period.getUnit();
        long time = period.getTime();
        switch (unit) {
            case "milliseconds":
                ssb.withIntervalInMilliseconds(time);
                break;
            case "seconds":
                ssb.withIntervalInSeconds((int) time);
                break;
            case "minutes":
                ssb.withIntervalInMinutes((int) time);
                break;
            case "hours":
                ssb.withIntervalInHours((int) time);
                break;
            case "days":
                ssb.withIntervalInHours((int) time * 24);
                break;
            default:
                break;
        }
        ssb.withRepeatCount(repeatCount);
        return ssb;
    }
2.2.2.3 構建SimpleTrigger

利用上述生成的SimpleScheduleBuilder和傳入的參數,這裏用SimpleScheduleDTO封裝,構建相應的SimpleTrigger。代碼如下:

    /**
     * 構建 SimpleTrigger
     *
     * @param ssd 參數
     * @return Trigger
     */
    private Trigger getSimpleTrigger(SimpleScheduleDTO ssd) {
        String jobName = ssd.getJobName();
        String group = ssd.getGroup();
        int repeatCount = ssd.getRepeatCount();
        TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger()
                //設置jobName和group
                .withIdentity(jobName, group)
                //設置Schedule方式
                .withSchedule(getSimpeScheduleBuilder(ssd.getPeriod(), repeatCount));
        if (ssd.getStartTime() != 0) {
            //設置起始時間
            triggerBuilder.startAt(new Date(ssd.getStartTime()));
        } else {
            triggerBuilder.startNow();
        }
        if (ssd.getEndTime() != 0) {
            //設置終止時間
            triggerBuilder.endAt(new Date(ssd.getEndTime()));
        }
        return triggerBuilder.build();
    }
2.2.2.4 創建ScheduleJob

Quartz創建定時任務,通過JobDetail 和Trigger就可以創建,我們編寫createJob方法,通過傳入相應參數來實現創建定時任務的功能,代碼如下:

/**
     * 創建Job
     * @param jobClass 要調度的類名
     * @param sd 調度參數
     * @param jobDataMap 數據
     * @param trigger trigger
     * @return Schedule
     */
    private Schedule createJob(
            Class<? extends Job> jobClass,
            ScheduleDTO sd,
            JobDataMap jobDataMap,
            Trigger trigger
    ){
        String jobName = sd.getJobName();
        String group = sd.getGroup();
        //判斷記錄在數據庫是否存在
        Schedule schedule = scheduleRepository.findScheduleByJobNameAndGroupName(jobName, group);
        if (schedule == null) {
            schedule = new Schedule();
        } else {
            throw new RuntimeException("Schedule job already exists.");
        }
        String scheduleId = UUID.randomUUID().toString();
        try {
            if (jobDataMap == null) {
                jobDataMap = new JobDataMap();
            }
            jobDataMap.put("id", scheduleId);
            //創建JobDetail
            JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, group).usingJobData(jobDataMap).build();
            schedule.setId(scheduleId);
            schedule.setStatus(ScheduleStatusEnum.ACTIVATED);
            schedule.setJobName(jobName);
            schedule.setGroupName(group);
            schedule.setTriggerInfo(JSON.toJSONString(sd));
            schedule.setRecord(0);
            //保存記錄信息
            schedule = scheduleRepository.save(schedule);
            //調度執行定時任務
            scheduler.scheduleJob(jobDetail, trigger);
        } catch (Exception e) {
            log.error("Create schedule job error:{}", e.getMessage());
            throw new RuntimeException(e);
        }
        return schedule;
    }

編寫一個createSimpleJob方法,用於創建SimpleTrigger類型的Job

/**
     * 創建 simple schedule job
     *
     * @param jobClass   job class
     * @param ssd        參數
     * @param jobDataMap 數據
     * @return Schedule
     */
    public Schedule createSimpleJob(Class<? extends Job> jobClass,
                                    SimpleScheduleDTO ssd,
                                    JobDataMap jobDataMap) {
        Trigger trigger = getSimpleTrigger(ssd);
        return createJob(jobClass, ssd, jobDataMap, trigger);
    }
2.2.2.5 更新Simple Schedule Job
/**
 * 更新simple job
 *
 * @param scheduleId scheduleId
 * @param ssd        ssv
 * @return Schedule
 */
public Schedule updateSimpleJob(String scheduleId, SimpleScheduleDTO ssd) {
    Schedule schedule = getSchedule(scheduleId);
    return updateSimpleJob(schedule, ssd);
}

public Schedule updateSimpleJob(Schedule schedule, SimpleScheduleDTO ssd) {
    try {
        String jobName = schedule.getJobName();
        String groupName = schedule.getGroupName();
        JobKey jobKey = new JobKey(jobName, groupName);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        //先刪除
        scheduler.deleteJob(jobKey);
        //重新創建
        Trigger trigger = getSimpleTrigger(ssd);
        scheduler.scheduleJob(jobDetail, trigger);
        //更新元數據
        schedule.setRecord(0);
        schedule.setTriggerInfo(JSON.toJSONString(ssd));
        scheduleRepository.save(schedule);
    } catch (SchedulerException e) {
        log.error("Update simple schedule job error:{}", e.getMessage());
    }
    return schedule;
}

public Schedule getSchedule(String scheduleId) {
    Schedule schedule = scheduleRepository.findScheduleById(scheduleId);
    if (schedule == null) {
        throw new RuntimeException("Schedule job does not exist");
    }
    return schedule;
}
2.2.2.6 暫停job
s/**
 * 暫停某個job
 *
 * @param scheduleId id
 */
public Schedule pauseJob(String scheduleId) {
    Schedule schedule = getSchedule(scheduleId);
    return pauseJob(schedule);
}

public Schedule pauseJob(Schedule schedule) {
    JobKey jobKey = new JobKey(schedule.getJobName(), schedule.getGroupName());
    try {
        scheduler.pauseJob(jobKey);
        schedule.setStatus(ScheduleStatusEnum.INACTIVATED);
        scheduleRepository.save(schedule);
    } catch (SchedulerException e) {
        log.error("Pause schedule job error:{}", e.getMessage());
    }
    return schedule;
}
2.2.2.7 恢復job
/**
 * 恢復某個job
 *
 * @param scheduleId id
 */
public Schedule resumeJob(String scheduleId) {
    Schedule schedule = getSchedule(scheduleId);
    return resumeJob(schedule);
}

public Schedule resumeJob(Schedule schedule) {
    JobKey jobKey = new JobKey(schedule.getJobName(), schedule.getGroupName());
    try {
        scheduler.resumeJob(jobKey);
        schedule.setStatus(ScheduleStatusEnum.ACTIVATED);
        scheduleRepository.save(schedule);
    } catch (SchedulerException e) {
        log.error("Resume schedule job error:{}", e.getMessage());
    }
    return schedule;
}
2.2.2.8 刪除job
/**
 * 刪除 job
 *
 * @param scheduleId id
 */
public void deleteJob(String scheduleId) {
    Schedule schedule = getSchedule(scheduleId);
    deleteJob(schedule);
}

public void deleteJob(Schedule schedule) {
    JobKey jobKey = new JobKey(schedule.getJobName(), schedule.getGroupName());
    try {
        scheduler.deleteJob(jobKey);
        scheduleRepository.delete(schedule);
    } catch (SchedulerException e) {
        log.error("Delete schedule job error:{}", e.getMessage());
    }
}

2.3 對外提供相關API

2.3.1 創建ScheduleService

創建ScheduleService,調用ScheduleManager分裝的接口。

package com.example.quartz.service;

import com.example.quartz.dto.SimpleScheduleDTO;
import com.example.quartz.entity.Schedule;
import com.example.quartz.job.HelloJob;
import com.example.quartz.manager.ScheduleManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * Created by shirukai on 2018/9/7
 */
@Service
public class ScheduleService {
    private final static String GROUP = "TEST_GROUP";
    @Autowired
    ScheduleManager scheduleManager;
    private final Logger log = LoggerFactory.getLogger(this.getClass());

    /**
     * 設置編排定時任務
     *
     * @param ssd 定時參數
     * @return schedule
     */
    public Schedule setSchedule(String joName, SimpleScheduleDTO ssd) {
        ssd.setJobName(joName);
        ssd.setGroup(GROUP);
        return scheduleManager.createSimpleJob(HelloJob.class, ssd, null);
    }


    /**
     * 更新編排定時任務
     *
     * @param ssd 參數
     * @return schedule
     */
    public Schedule modifySchedule(String jobName, SimpleScheduleDTO ssd) {
        Schedule schedule = scheduleManager.getJobByNameAndGroup(jobName, GROUP);
        ssd.setJobName(jobName);
        ssd.setGroup(GROUP);
        return scheduleManager.updateSimpleJob(schedule, ssd);
    }

    /**
     * 獲取編排定時信息
     *
     * @param joName id
     * @return schedule
     */
    public Schedule getSchedule(String joName) {
        return scheduleManager.getJobByNameAndGroup(joName, GROUP);
    }

    /**
     * 暫停編排定時任務
     *
     * @param joName joName
     * @return schedule
     */
    public Schedule pauseSchedule(String joName) {
        Schedule schedule = scheduleManager.getJobByNameAndGroup(joName, GROUP);
        return scheduleManager.pauseJob(schedule);
    }

    /**
     * 恢復編排定時任務
     *
     * @param joName joName
     * @return schedule
     */
    public Schedule resumeSchedule(String joName) {
        Schedule schedule = scheduleManager.getJobByNameAndGroup(joName, GROUP);
        return scheduleManager.resumeJob(schedule);
    }

    /**
     * 刪除編排定時任務
     *
     * @param joName joName
     * @return str
     */
    public String removerSchedule(String joName) {
        Schedule schedule = scheduleManager.getJobByNameAndGroup(joName, GROUP);
        scheduleManager.deleteJob(schedule);
        return "Delete schedule job succeed.";
    }
}

2.3.2 創建SceduleController

創建SceduleCOntroller對外提供可訪問API

package com.example.quartz.controller;


import com.example.quartz.common.rest.RestMessage;
import com.example.quartz.common.util.RestMessageUtil;
import com.example.quartz.dto.SimpleScheduleDTO;
import com.example.quartz.service.ScheduleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * Created by shirukai on 2018/9/7
 */
@RestController
@RequestMapping(value = "/api/v1/schedule/")
public class ScheduleController {
    @Autowired
    ScheduleService scheduleService;
    @PostMapping(value = "/{jobName}/simple")
    public RestMessage schedule(
            @PathVariable("jobName") String jobName,
            @RequestBody SimpleScheduleDTO simpleScheduleDTO
    ) {
        return RestMessageUtil.objectToRestMessage(scheduleService.setSchedule(jobName, simpleScheduleDTO));
    }

    @PutMapping(value = "/{jobName}/simple")
    public RestMessage modifySchedule(
            @PathVariable("jobName") String jobName,
            @RequestBody SimpleScheduleDTO simpleScheduleDTO
    ) {
        return RestMessageUtil.objectToRestMessage(scheduleService.modifySchedule(jobName, simpleScheduleDTO));
    }

    @DeleteMapping(value = "/{jobName}")
    public RestMessage removeSchedule(
            @PathVariable("jobName") String jobName
    ) {
        return RestMessageUtil.objectToRestMessage(scheduleService.removerSchedule(jobName));
    }

    @PostMapping(value = "/{jobName}/pause")
    public RestMessage pauseSchedule(
            @PathVariable("jobName") String jobName
    ) {
        return RestMessageUtil.objectToRestMessage(scheduleService.pauseSchedule(jobName));
    }

    @PostMapping(value = "/{jobName}/resume")
    public RestMessage resumeSchedule(
            @PathVariable("jobName") String jobName
    ) {
        return RestMessageUtil.objectToRestMessage(scheduleService.resumeSchedule(jobName));
    }

    @GetMapping(value = "/{jobName}")
    public RestMessage scheduleInfo(
            @PathVariable("jobName") String jobName
    ) {
        return RestMessageUtil.objectToRestMessage(scheduleService.getSchedule(jobName));
    }
}

2.3.3 測試提供的API

在這之前先修改我們的HelloJob類,用以記錄我們執行的條數

package com.example.quartz.job;

import com.example.quartz.entity.Schedule;
import com.example.quartz.repository.ScheduleRepository;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Created by shirukai on 2018/9/7
 */
public class HelloJob implements Job {
    @Autowired
    ScheduleRepository scheduleRepository;
    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        //獲取上下文數據
        JobDataMap dataMap = context.getMergedJobDataMap();
        String scheduleId = dataMap.getString("id");

        Schedule schedule = scheduleRepository.findScheduleById(scheduleId);
        log.info("定時任務執行了:{}", scheduleId);
        //更新執行條數
        schedule.setRecord(schedule.getRecord() + 1);
        scheduleRepository.save(schedule);
    }
}
2.3.3.1 設置定時任務

請求API:{{hostIP}}/api/v1/schedule/8aaabdfc659f74620165b2ad36b50030/simple

請求類型:POST

請求參數:

{
    "repeatCount": 100,
    "period": {
        "time": "5",
        "unit": "seconds"
    }
}

響應:

{
    "success": true,
    "code": 0,
    "msg": "操作成功",
    "data": {
        "id": "218a3db4-e2e6-480e-b520-bac138b6c403",
        "triggerInfo": "{\"endTime\":0,\"group\":\"TEST_GROUP\",\"jobName\":\"8aaabdfc659f74620165b2ad36b50030\",\"period\":{\"time\":5,\"unit\":\"seconds\"},\"repeatCount\":100,\"startTime\":0}",
        "status": "ACTIVATED",
        "groupName": "TEST_GROUP",
        "jobName": "8aaabdfc659f74620165b2ad36b50030",
        "record": 0,
        "createdTimestamp": "2018-09-07T06:50:15.521+0000"
    }
}
2.3.3.2 查看定時任務

請求API:{{hostIP}}/api/v1/schedule/8aaabdfc659f74620165b2ad36b50030/

請求類型:GET

請求參數:無

響應:

{
    "success": true,
    "code": 0,
    "msg": "操作成功",
    "data": {
        "id": "218a3db4-e2e6-480e-b520-bac138b6c403",
        "triggerInfo": "{\"endTime\":0,\"group\":\"TEST_GROUP\",\"jobName\":\"8aaabdfc659f74620165b2ad36b50030\",\"period\":{\"time\":5,\"unit\":\"seconds\"},\"repeatCount\":100,\"startTime\":0}",
        "status": "ACTIVATED",
        "groupName": "TEST_GROUP",
        "jobName": "8aaabdfc659f74620165b2ad36b50030",
        "record": 9,
        "createdTimestamp": "2018-09-07T06:50:16.000+0000"
    }
}
2.3.3.3 暫停定時任務

請求API:{{hostIP}}/api/v1/schedule/8aaabdfc659f74620165b2ad36b50030/pause

請求類型:POST

請求參數:無

響應:

{
    "success": true,
    "code": 0,
    "msg": "操作成功",
    "data": {
        "id": "218a3db4-e2e6-480e-b520-bac138b6c403",
        "triggerInfo": "{\"endTime\":0,\"group\":\"TEST_GROUP\",\"jobName\":\"8aaabdfc659f74620165b2ad36b50030\",\"period\":{\"time\":5,\"unit\":\"seconds\"},\"repeatCount\":100,\"startTime\":0}",
        "status": "INACTIVATED",
        "groupName": "TEST_GROUP",
        "jobName": "8aaabdfc659f74620165b2ad36b50030",
        "record": 23,
        "createdTimestamp": "2018-09-07T06:50:16.000+0000"
    }
}
2.3.3.4 恢復定時任務

請求API:{{hostIP}}/api/v1/schedule/8aaabdfc659f74620165b2ad36b50030/resume

請求類型:POST

請求參數:無

響應:

{
    "success": true,
    "code": 0,
    "msg": "操作成功",
    "data": {
        "id": "218a3db4-e2e6-480e-b520-bac138b6c403",
        "triggerInfo": "{\"endTime\":0,\"group\":\"TEST_GROUP\",\"jobName\":\"8aaabdfc659f74620165b2ad36b50030\",\"period\":{\"time\":5,\"unit\":\"seconds\"},\"repeatCount\":100,\"startTime\":0}",
        "status": "ACTIVATED",
        "groupName": "TEST_GROUP",
        "jobName": "8aaabdfc659f74620165b2ad36b50030",
        "record": 23,
        "createdTimestamp": "2018-09-07T06:50:16.000+0000"
    }
}
2.3.3. 5 刪除定時任務

請求API:{{hostIP}}/api/v1/schedule/8aaabdfc659f74620165b2ad36b50030/

請求類型:DELETE

請求參數:無

響應:

{
    "success": true,
    "code": 0,
    "msg": "操作成功",
    "data": "Delete schedule job succeed."
}

3 實現基於CronTrigger的定時任務

上面我們已經實現了基於SimpleTrigger的定時任務管理,從Quartz封裝,到對外提供RESTful接口。實現了的定時任務的添加、暫停、恢復、查看、刪除等功能。接下來我們在此基礎上,繼續對Quartz進行分裝,實現基於CronTrigger的定時任務。

3.1 基於CronTrigger分裝Quartz相關操作

3.1.1 創建CronScheduleDTO.java實體類

與SimpleScheduleDTO一樣需要繼承ScheduleDTO類,代碼如下:

package com.example.quartz.dto;

/**
 * Created by shirukai on 2018/9/7
 */
public class CronScheduleDTO extends ScheduleDTO {
    private String cronExpression;

    public String getCronExpression() {
        return cronExpression;
    }

    public void setCronExpression(String cronExpression) {
        this.cronExpression = cronExpression;
    }
}

3.1.2 構建CronTrigger

在ScheduleManager類裏添加getCronTrigger方法,用於構建CronTrigger

    private Trigger getCronTrigger(CronScheduleDTO csd) {
        CronScheduleBuilder scb = CronScheduleBuilder.cronSchedule(csd.getCronExpression());
        TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger()
                .withIdentity(csd.getJobName(), csd.getGroup())
                .withSchedule(scb);
        if (csd.getStartTime() != 0) {
            triggerBuilder.startAt(new Date(csd.getStartTime()));
        } else {
            triggerBuilder.startNow();
        }
        if (csd.getEndTime() != 0) {
            triggerBuilder.endAt(new Date(csd.getEndTime()));
        }
        return triggerBuilder.build();
    }

3.1.3 構建Cron Schedule Job

在ScheduleManager類裏添加createCronJob方法

/**
 * 創建 cron schedule job
 *
 * @param jobClass   可執行job class
 * @param csd        定時參數
 * @param jobDataMap 數據
 * @return Schedule
 */
public Schedule createCronJob(Class<? extends Job> jobClass, CronScheduleDTO csd, JobDataMap jobDataMap) {
    Trigger trigger = getCronTrigger(csd);
    return createJob(jobClass, csd, jobDataMap, trigger);
}

3.1.4 更新Job

在ScheduleManager類裏添加updateCronJob方法

public Schedule updateCronJob(String scheduleId, CronScheduleDTO csd) {
    Schedule schedule = getSchedule(scheduleId);
    return updateCronJob(schedule, csd);
}

public Schedule updateCronJob(Schedule schedule, CronScheduleDTO csd) {
    try {
        String jobName = schedule.getJobName();
        String groupName = schedule.getGroupName();
        JobKey jobKey = new JobKey(jobName, groupName);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        //先刪除
        scheduler.deleteJob(jobKey);
        //重新創建
        Trigger trigger = getCronTrigger(csd);
        scheduler.scheduleJob(jobDetail, trigger);
        //更新元數據
        schedule.setRecord(0);
        schedule.setTriggerInfo(JSON.toJSONString(csd));
        scheduleRepository.save(schedule);
    } catch (SchedulerException e) {
        log.error("Update cron schedule job error:{}", e.getMessage());
    }
    return schedule;
}

3.2 對外提供相關API

3.3.1 修改ScheduleService

在ScheduleService類裏添加cron相關的操作,主要是添加Cron定時任務和更新Cron定時任務,代碼如下:

    public Schedule setSchedule(String jobName, CronScheduleDTO csd) {
        csd.setJobName(jobName);
        csd.setGroup(GROUP);
        return scheduleManager.createCronJob(HelloJob.class, csd, null);
    }
    public Schedule modifySchedule(String jobName, CronScheduleDTO csd) {
        Schedule schedule = scheduleManager.getJobByNameAndGroup(jobName, GROUP);
        csd.setJobName(jobName);
        csd.setGroup(GROUP);
        return scheduleManager.updateCronJob(schedule, csd);
    }

3.3.2 修改ScheduleController

同樣在ScheduleController下添加cron相應的接口

    @PostMapping(value = "/{jobName}/cron")
    public RestMessage schedule(
            @PathVariable("jobName") String jobName,
            @RequestBody CronScheduleDTO cronScheduleDTO
    ) {
        return RestMessageUtil.objectToRestMessage(scheduleService.setSchedule(jobName, cronScheduleDTO));
    }

    @PutMapping(value = "/{jobName}/cron")
    public RestMessage modifySchedule(
            @PathVariable("jobName") String jobName,
            @RequestBody CronScheduleDTO cronScheduleDTO
    ) {
        return RestMessageUtil.objectToRestMessage(scheduleService.modifySchedule(jobName, cronScheduleDTO));
    }

3.3.3 測試提供的API

3.3.3.1 設置cron格式的定時任務

請求API:{{hostIP}}/api/v1/schedule/8aaabdfc659f74620165b2ad36b50030/cron

請求類型:POST

請求參數:

{
    "cronExpression":"0 0/5 * * * ? *"
}

響應:

{
    "success": true,
    "code": 0,
    "msg": "操作成功",
    "data": {
        "id": "2d876e3e-f1e1-4ad7-bc64-5a107387cd3f",
        "triggerInfo": "{\"cronExpression\":\"0 0/5 * * * ? *\",\"endTime\":0,\"group\":\"TEST_GROUP\",\"jobName\":\"8aaabdfc659f74620165b2ad36b50030\",\"startTime\":0}",
        "status": "ACTIVATED",
        "groupName": "TEST_GROUP",
        "jobName": "8aaabdfc659f74620165b2ad36b50030",
        "record": 0,
        "createdTimestamp": "2018-09-07T07:32:40.238+0000"
    }
}
3.3.3.2 更新cron格式的定時任務

請求API:{{hostIP}}/api/v1/schedule/8aaabdfc659f74620165b2ad36b50030/cron

請求類型:PUT

請求參數:

{
    "cronExpression":"0/1 * * * * ? "
}

響應:

{
    "success": true,
    "code": 0,
    "msg": "操作成功",
    "data": {
        "id": "2d876e3e-f1e1-4ad7-bc64-5a107387cd3f",
        "triggerInfo": "{\"cronExpression\":\"0/1 * * * * ? \",\"endTime\":0,\"group\":\"TEST_GROUP\",\"jobName\":\"8aaabdfc659f74620165b2ad36b50030\",\"startTime\":0}",
        "status": "ACTIVATED",
        "groupName": "TEST_GROUP",
        "jobName": "8aaabdfc659f74620165b2ad36b50030",
        "record": 0,
        "createdTimestamp": "2018-09-07T07:32:40.000+0000"
    }
}

4 Springboot整合Quartz進階

4.1 自動初始化Quartz建表SQL

4.1.1 使用Springboot的DataSource時,初始化Quartz建表SQL

使用springboot的DataSource初始化SQL我這裏提供了兩種方式,一種是基於配置的SQL初始化、另一種是基於編程的SQL初始化。下面將分別記錄一下這兩種初始化SQL的方式。

4.1.1.1 基於配置的SQL初始化

基於配置的SQL初始化很簡單,只需要在springboot中添加幾個配置項即可。

首先列一下我們的初始化腳本:

CREATE TABLE IF NOT EXISTS QRTZ_JOB_DETAILS(
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) 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))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS QRTZ_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) 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(200) 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))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS  QRTZ_SIMPLE_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) 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))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS  QRTZ_CRON_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CRON_EXPRESSION VARCHAR(120) 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))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS  QRTZ_SIMPROP_TRIGGERS
  (          
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) 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))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS  QRTZ_BLOB_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS  QRTZ_CALENDARS (
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS  QRTZ_PAUSED_TRIGGER_GRPS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS  QRTZ_FIRED_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
INSTANCE_NAME VARCHAR(200) 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(200) NULL,
JOB_GROUP VARCHAR(200) NULL,
IS_NONCONCURRENT VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS  QRTZ_SCHEDULER_STATE (
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS  QRTZ_LOCKS (
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME))
ENGINE=InnoDB;

可以看出,我們的初始化腳本,是基於官網提供的建表腳本進行了改造。改造有兩點:第一點是去掉原有的如果表存在則刪除的腳本,改爲如果表不存在則創建。第二點是去掉索引腳本,因爲索引重複創建會報錯。這個可以利用存儲過程的方式去解決,後面會提到。下面將我們的初始化SQL的腳本配置到springboot的配置文件中。在application.yml配置文件中,添加如下內容:

spring:
 #配置數據庫
 datasource:
   driver-class-name: com.mysql.jdbc.Driver
   url: jdbc:mysql://localhost:3306/springboot?useSSL=false&characterEncoding=utf-8
   username: root
   password: hollysys
   schema-username: root
   schema-password: hollysys
   schema: classpath:quartz_tables.sql
   initialization-mode: always

這樣的話,系統就會默認初始化我們的sql了。

注意:在springboot2.0之前不需要指定schema-username、schema-password、initialization-mode這三個屬性就可以初始化sql,但是在2.0之後必須要設置這三個屬性,否則spring.datasource.schema屬性無法正常執行。

補充配置帶存儲過程的SQL腳本:

帶添加索引的SQL腳本:


CREATE TABLE IF NOT EXISTS  QRTZ_JOB_DETAILS (
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) 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))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS QRTZ_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) 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(200) 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))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS QRTZ_SIMPLE_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) 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))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS QRTZ_CRON_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CRON_EXPRESSION VARCHAR(120) 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))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS QRTZ_SIMPROP_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) 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))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS QRTZ_BLOB_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS QRTZ_CALENDARS (
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS QRTZ_PAUSED_TRIGGER_GRPS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS QRTZ_FIRED_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
INSTANCE_NAME VARCHAR(200) 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(200) NULL,
JOB_GROUP VARCHAR(200) NULL,
IS_NONCONCURRENT VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS QRTZ_SCHEDULER_STATE (
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS QRTZ_LOCKS (
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME))
ENGINE=InnoDB;


DROP PROCEDURE IF EXISTS schema_change;
CREATE PROCEDURE schema_change()
DELIMITER $$
BEGIN
DECLARE  CurrentDatabase VARCHAR(100);
SELECT DATABASE() INTO CurrentDatabase;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_JOB_DETAILS' AND index_name = 'IDX_QRTZ_J_REQ_RECOVERY') THEN
  CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);
END IF;

IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_JOB_DETAILS' AND index_name = 'IDX_QRTZ_J_GRP') THEN
  CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);
END IF;

IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_J') THEN
  CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
END IF;

IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_JG') THEN
  CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_C') THEN
  CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_G') THEN
  CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_STATE') THEN
  CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_N_STATE') THEN
  CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_N_G_STATE') THEN
  CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_NEXT_FIRE_TIME') THEN
  CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_NFT_ST') THEN
  CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_NFT_MISFIRE') THEN
  CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
END IF;

IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_NFT_ST_MISFIRE') THEN
  CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_NFT_ST_MISFIRE_GRP') THEN
  CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_FIRED_TRIGGERS' AND index_name = 'IDX_QRTZ_FT_TRIG_INST_NAME') THEN
  CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_FIRED_TRIGGERS' AND index_name = 'IDX_QRTZ_FT_INST_JOB_REQ_RCVRY') THEN
  CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_FIRED_TRIGGERS' AND index_name = 'IDX_QRTZ_FT_J_G') THEN
  CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_FIRED_TRIGGERS' AND index_name = 'IDX_QRTZ_FT_JG') THEN
  CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);
END IF;

IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_FIRED_TRIGGERS' AND index_name = 'IDX_QRTZ_FT_T_G') THEN
  CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_FIRED_TRIGGERS' AND index_name = 'IDX_QRTZ_FT_TG') THEN
  CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
END IF;

END $$
DELIMITER;
CALL schema_change();

剛纔說到基於配置的初始化SQL腳本不能去執行存儲過程,原因是,springboot默認的SQL分隔符爲;,也就是說,當它讀到腳本中的;時就會默認爲這是一條可執行的語句,所以我們的存儲過程就沒有辦法執行,就會報如下錯誤:

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; 
check the manual that corresponds to your MySQL server version for the right syntax to use near
'$$ BEGIN DECLARE CurrentDatabase VARCHAR(100)' at line 1

顯然就是說我們的SQL語法有問題,實際上就是上面我所提到的問題導致的,那麼如何解決呢,這裏參考了網上一種解決方法就是修改springboot默認的SQL分隔符。這裏我們修改分隔符爲$$。在配置文件中添加:

spirng.datasource.separator: $$

然後就是修改我們的SQL腳本,將原來的;改爲;$$,存儲過程中的;不變。

腳本如下所示:

CREATE TABLE IF NOT EXISTS  QRTZ_JOB_DETAILS (
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) 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))
ENGINE=InnoDB;$$

CREATE TABLE IF NOT EXISTS QRTZ_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) 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(200) 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))
ENGINE=InnoDB;$$

CREATE TABLE IF NOT EXISTS QRTZ_SIMPLE_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) 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))
ENGINE=InnoDB;$$

CREATE TABLE IF NOT EXISTS QRTZ_CRON_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CRON_EXPRESSION VARCHAR(120) 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))
ENGINE=InnoDB;$$

CREATE TABLE IF NOT EXISTS QRTZ_SIMPROP_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) 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))
ENGINE=InnoDB;$$

CREATE TABLE IF NOT EXISTS QRTZ_BLOB_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;$$

CREATE TABLE IF NOT EXISTS QRTZ_CALENDARS (
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))
ENGINE=InnoDB;$$

CREATE TABLE IF NOT EXISTS QRTZ_PAUSED_TRIGGER_GRPS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;$$

CREATE TABLE IF NOT EXISTS QRTZ_FIRED_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
INSTANCE_NAME VARCHAR(200) 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(200) NULL,
JOB_GROUP VARCHAR(200) NULL,
IS_NONCONCURRENT VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID))
ENGINE=InnoDB;$$

CREATE TABLE IF NOT EXISTS QRTZ_SCHEDULER_STATE (
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))
ENGINE=InnoDB;$$

CREATE TABLE IF NOT EXISTS QRTZ_LOCKS (
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME))
ENGINE=InnoDB;$$

DROP PROCEDURE IF EXISTS schema_change;$$
CREATE PROCEDURE schema_change()
BEGIN
DECLARE  CurrentDatabase VARCHAR(100);
SELECT DATABASE() INTO CurrentDatabase;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_JOB_DETAILS' AND index_name = 'IDX_QRTZ_J_REQ_RECOVERY') THEN
  CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);
END IF;

IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_JOB_DETAILS' AND index_name = 'IDX_QRTZ_J_GRP') THEN
  CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);
END IF;

IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_J') THEN
  CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
END IF;

IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_JG') THEN
  CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_C') THEN
  CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_G') THEN
  CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_STATE') THEN
  CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_N_STATE') THEN
  CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_N_G_STATE') THEN
  CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_NEXT_FIRE_TIME') THEN
  CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_NFT_ST') THEN
  CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_NFT_MISFIRE') THEN
  CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
END IF;

IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_NFT_ST_MISFIRE') THEN
  CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_TRIGGERS' AND index_name = 'IDX_QRTZ_T_NFT_ST_MISFIRE_GRP') THEN
  CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_FIRED_TRIGGERS' AND index_name = 'IDX_QRTZ_FT_TRIG_INST_NAME') THEN
  CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_FIRED_TRIGGERS' AND index_name = 'IDX_QRTZ_FT_INST_JOB_REQ_RCVRY') THEN
  CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_FIRED_TRIGGERS' AND index_name = 'IDX_QRTZ_FT_J_G') THEN
  CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_FIRED_TRIGGERS' AND index_name = 'IDX_QRTZ_FT_JG') THEN
  CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);
END IF;

IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_FIRED_TRIGGERS' AND index_name = 'IDX_QRTZ_FT_T_G') THEN
  CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
END IF;
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema=CurrentDatabase AND table_name = 'QRTZ_FIRED_TRIGGERS' AND index_name = 'IDX_QRTZ_FT_TG') THEN
  CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
END IF;

END $$
CALL schema_change();$$

如上修改完我們的SQL腳本之後,就可以在啓動項目的時候自動初始化項目了 。

4.1.1.2 基於編程的SQL初始化

基於編程的SQL初始化就是手動加載DataSource和SQL腳本,然後去執行腳本。因爲我們之前將Quartz與Spring整合,需要將Scheduler以Bean的形式注入到IOC容器中,所以我們的初始化腳本要在這之前執行。這裏我們將在之前的QuartzConfig類裏添加相應修改。

註釋掉之前的基於配置的SQL初始化配置

#   schema-username: root
#   schema-password: hollysys
#   schema: classpath:quartz_tables.sql
#   initialization-mode: always
#   separator: $$

在QuartzConfig類裏注入Springboot的DataSource

    @Autowired
    DataSource dataSource;

在QuartzConfig類創建初始化數據庫方法initDataBase()

public void initDataBase(DataSource dataSource) {
        log.info("============== init quartz database started ==============");
        try {
            //加載SQL
            ClassPathResource recordsSys = new ClassPathResource("quartz_tables.sql");
            //使用DataSourceInitializer初始化
            DataSourceInitializer dsi = new DataSourceInitializer();
            dsi.setDataSource(dataSource);
            dsi.setDatabasePopulator(new ResourceDatabasePopulator(true, true, "utf-8", recordsSys));
            dsi.setEnabled(true);
            dsi.afterPropertiesSet();
            log.info("============== init quartz database succeed ==============");
        } catch (Exception e) {
            log.error("init quartz database failed:{}", e.getMessage());
        }
    }

在創建SchedulerFactoryBean的時候,去初始化數據庫

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
        //初始化數據庫
        initDataBase(dataSource);
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setOverwriteExistingJobs(true);
        schedulerFactoryBean.setQuartzProperties(quartzProperties());
        schedulerFactoryBean.setJobFactory(jobFactory);
        return schedulerFactoryBean;
    }

這樣我們就完成基於編程的SQL初始化。

4.1.2 使用Quartz自定義DataSource時,初始化Quartz建表SQL

上面我們介紹了兩種方式實現使用Springboot的DataSource去初始化Quartz的建表SQL。這裏我們來講一下,使用自定義的DataSource該如何初始化呢?其實原理與上面的基於編程的方式一樣,只不過不能通過註解的方式注入DataSource,而是需要我們手動創建DataSource,然後傳入上面編寫的initDataBase()方法。

首先在quartz.properties配置文件中開啓我們的數據庫相關配置

#存儲方式使用JobStoreTX,也就是數據庫
org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#是否使用集羣(如果項目只部署到 一臺服務器,就不用了)
org.quartz.jobStore.isClustered = false
org.quartz.jobStore.clusterCheckinInterval=20000
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.dataSource = myDS

#配置數據源
#數據庫中quartz表的表名前綴
org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/springboot?characterEncoding=utf-8&useSSL=false
org.quartz.dataSource.myDS.user = root
org.quartz.dataSource.myDS.password = hollysys
org.quartz.dataSource.myDS.maxConnections = 5

在QuartzConfig類裏添加創建DataSource的方法 quartzSource()如下所示:

    @Bean
    public DataSource quartzSource() throws IOException {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        Properties properties = quartzProperties();
        dataSource.setDriverClassName(properties.getProperty("org.quartz.dataSource.myDS.driver"));
        dataSource.setUrl(properties.getProperty("org.quartz.dataSource.myDS.URL"));
        dataSource.setUsername(properties.getProperty("org.quartz.dataSource.myDS.user"));
        dataSource.setPassword(properties.getProperty("org.quartz.dataSource.myDS.password"));
        return dataSource;
    }

然後同樣在創建SchedulerFactoryBean的時候,去初始化數據庫

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
        //初始化數據庫,這時候DataSource就要使用上面的quartzSource()方法創建了
        initDataBase(quartzSource());
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setOverwriteExistingJobs(true);
        schedulerFactoryBean.setQuartzProperties(quartzProperties());
        schedulerFactoryBean.setJobFactory(jobFactory);
        return schedulerFactoryBean;
    }

這樣我們就可以實現基於自定義DataSource的SQL初始化。

4.2 關於實現多租戶的幾點思路

4.2.1 租戶獨享服務模式的實現

這裏所說的租戶獨享服務包含兩種情景:一種是一個租戶對應一個服務,另一種是一個租戶對應多個服務(服務集羣)即一個租戶對應一個集羣。這兩種場景的實現方式都可以用一種方案解決。我的思路是使用Scheduler容器來區分租戶,即一個租戶對應一個Scheduler容器或一個Scheduler容器集羣。該租戶的Scheduler容器,只會管理在該Scheduler實例中創建的定時任務。生產中,我們的租戶以服務作爲隔離級別,租戶各自有自己的服務。Quartz集羣以Scheduler容器作爲隔離級別,租戶各自的定時任務在自己的Scheduler容器和Scheduler集羣裏執行。

那麼如何通過Scheduler去做租戶隔離呢?其實很簡單,只需要在創建SchedulerFactoryBean的時候,指定一下SchedulerName即可,這個地方我們用租戶ID來做SchedulerName,從而實現基於Scheduler容器的多租戶。

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
        //初始化數據庫
        initDataBase(dataSource);
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setOverwriteExistingJobs(true);
        schedulerFactoryBean.setQuartzProperties(quartzProperties());
        schedulerFactoryBean.setJobFactory(jobFactory);
        //通過租戶ID來設置SchedulerName從而實現多租戶
        schedulerFactoryBean.setSchedulerName(tenant_id);
        return schedulerFactoryBean;
    }

這樣我們租戶下的服務只會執行自己租戶ID對應的Scheduler容器中的定時任務。

4.2.1 租戶共享服務模式的實現

租戶共享服務模式是指一個服務對應多個租戶,或者一個服務集羣對應多個租戶。這樣的服務模式原本隔離性就很差。這樣的多租戶實現,可以使用JobGroup來進行隔離。使用同一個Scheduler容器或者一個Scheduler集羣來共同調度所有的定時任務,只不過是通過JobGroup來區分,哪個定時任務對應哪個租戶。這時候我們就可以使用租戶ID來作爲group名就能實現。具體實現,根據自己的業務場景定製,這裏不做演示。

5 總結

至此,已經完成了所有Springboot整合Quartz實現定時任務調度管理的內容。項目的實現我也是從0到1,從接觸Quartz調度框架到整合到自己的項目中,其中一些思路來自於強大的網絡,另一些是自己不成熟的見解。希望記錄整合過程,對以後開發有所幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章