第三十九章:基於SpringBoot & Quartz完成定時任務分散式單節點持久化

恆宇少年發表於2017-11-15

定時任務在企業專案比較常用到,幾乎所有的專案都會牽扯該功能模組,定時任務一般會處理指定時間點執行某一些業務邏輯、間隔時間執行某一些業務邏輯等。我們在之前有講過SpringBoot是已經整合了定時任務的,詳見:第二十六章:SpringBoot使用@Scheduled建立定時任務,那麼我們本章將會採用外接的quartz定時任務框架來完成定時任務的分散式單節點持久化,我們為什麼要持久化定時任務呢?

在一些專案中定時任務可能是必不可少的,由於某種特殊的原因定時任務可能丟失,如重啟定時任務服務專案後,原記憶體中的定時任務就會被完全釋放!那對於我們來說可能是致命的問題。當然也有強制的辦法解決這類問題,但是如果我們把定時任務持久化到資料庫,像維護普通邏輯資料那樣維護任務,就會避免專案中遇到的種種的特殊情況。

本章目標

基於SpringBoot架構整合定時任務框架quartz來完成分散式單節點定時任務持久化,將任務持久化到資料庫,更好的預防任務丟失。

構建專案

我們使用idea開發工具建立一個SpringBoot專案,pom.xml依賴配置如下所示:

...省略部分配置
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <druid.version>1.1.5</druid.version>
        <quartz.version>2.3.0</quartz.version>
    </properties>

    <dependencies>
        <!--spring data jpa相關-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--web相關依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--資料庫相關依賴-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <!--quartz相關依賴-->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>${quartz.version}</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>${quartz.version}</version>
        </dependency>
        <!--定時任務需要依賴context模組-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
...省略部分配置複製程式碼

我們採用的是quartz官方最新版本2.3.0,新版本的任務排程框架做出了很多封裝,使用也變得簡易明瞭。
建立初始化完成,下面我們來建立定時任務相關的Configuration配置。

QuartzConfiguration

quartzSpring相關框架的整合方式有很多種,我們今天採用jobDetail使用Spring Ioc託管方式來完成整合,我們可以在定時任務例項中使用Spring注入註解完成業務邏輯處理,下面我先把全部的配置貼出來再逐步分析,配置類如下所示:

package com.hengyu.chapter39.configuration;

import org.quartz.spi.JobFactory;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

import javax.sql.DataSource;

/**
 * quartz定時任務配置
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/11/5
 * Time:14:07
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 * @author  恆宇少年
 */
@Configuration
@EnableScheduling
public class QuartzConfiguration
{
    /**
     * 繼承org.springframework.scheduling.quartz.SpringBeanJobFactory
     * 實現任務例項化方式
     */
    public static class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
            ApplicationContextAware {

        private transient AutowireCapableBeanFactory beanFactory;

        @Override
        public void setApplicationContext(final ApplicationContext context) {
            beanFactory = context.getAutowireCapableBeanFactory();
        }

        /**
         * 將job例項交給spring ioc託管
         * 我們在job例項實現類內可以直接使用spring注入的呼叫被spring ioc管理的例項
         * @param bundle
         * @return
         * @throws Exception
         */
        @Override
        protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
            final Object job = super.createJobInstance(bundle);
            /**
             * 將job例項交付給spring ioc
             */
            beanFactory.autowireBean(job);
            return job;
        }
    }

    /**
     * 配置任務工廠例項
     * @param applicationContext spring上下文例項
     * @return
     */
    @Bean
    public JobFactory jobFactory(ApplicationContext applicationContext)
    {
        /**
         * 採用自定義任務工廠 整合spring例項來完成構建任務
         * see {@link AutowiringSpringBeanJobFactory}
         */
        AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
        jobFactory.setApplicationContext(applicationContext);
        return jobFactory;
    }

    /**
     * 配置任務排程器
     * 使用專案資料來源作為quartz資料來源
     * @param jobFactory 自定義配置任務工廠
     * @param dataSource 資料來源例項
     * @return
     * @throws Exception
     */
    @Bean(destroyMethod = "destroy",autowire = Autowire.NO)
    public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, DataSource dataSource) throws Exception
    {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        //將spring管理job自定義工廠交由排程器維護
        schedulerFactoryBean.setJobFactory(jobFactory);
        //設定覆蓋已存在的任務
        schedulerFactoryBean.setOverwriteExistingJobs(true);
        //專案啟動完成後,等待2秒後開始執行排程器初始化
        schedulerFactoryBean.setStartupDelay(2);
        //設定排程器自動執行
        schedulerFactoryBean.setAutoStartup(true);
        //設定資料來源,使用與專案統一資料來源
        schedulerFactoryBean.setDataSource(dataSource);
        //設定上下文spring bean name
        schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext");
        //設定配置檔案位置
        schedulerFactoryBean.setConfigLocation(new ClassPathResource("/quartz.properties"));
        return schedulerFactoryBean;
    }
}複製程式碼

AutowiringSpringBeanJobFactory

可以看到上面配置類中,AutowiringSpringBeanJobFactory我們繼承了SpringBeanJobFactory類,並且通過實現ApplicationContextAware介面獲取ApplicationContext設定方法,通過外部例項化時設定ApplicationContext例項物件,在createJobInstance方法內,我們採用AutowireCapableBeanFactory來託管SpringBeanJobFactory類中createJobInstance方法返回的定時任務例項,這樣我們就可以在定時任務類內使用Spring Ioc相關的註解進行注入業務邏輯例項了。

JobFactory

任務工廠是在本章配置排程器時所需要的例項,我們通過jobFactory方法注入ApplicationContext例項,來建立一個AutowiringSpringBeanJobFactory物件,並且將物件例項託管到Spring Ioc容器內。

SchedulerFactoryBean

我們本章採用的是專案內部資料來源的方式來設定排程器的jobSotre,官方quartz有兩種持久化的配置方案。

第一種:採用quartz.properties配置檔案配置獨立的定時任務資料來源,可以與使用專案的資料庫完全獨立。
第二種:採用與建立專案統一個資料來源,定時任務持久化相關的表與業務邏輯在同一個資料庫內。

可以根據實際的專案需求採取不同的方案,我們本章主要是通過第二種方案來進行講解,在上面配置類中可以看到方法schedulerFactoryBean內自動注入了JobFactory例項,也就是我們自定義的AutowiringSpringBeanJobFactory任務工廠例項,另外一個引數就是DataSource,在我們引入spring-starter-data-jpa依賴後會根據application.yml檔案內的資料來源相關配置自動例項化DataSource例項,這裡直接注入是沒有問題的。

我們通過呼叫SchedulerFactoryBean物件的setConfigLocation方法來設定quartz定時任務框架的基本配置,配置檔案所在位置:resources/quartz.properties => classpath:/quartz.properties下。

注意:quartz.properties配置檔案一定要放在classpath下,放在別的位置有部分功能不會生效。

下面我們來看下quartz.properties檔案內的配置,如下所示:

#排程器例項名稱
org.quartz.scheduler.instanceName = quartzScheduler

#排程器例項編號自動生成
org.quartz.scheduler.instanceId = AUTO

#持久化方式配置
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

#持久化方式配置資料驅動,MySQL資料庫
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate

#quartz相關資料表字首名
org.quartz.jobStore.tablePrefix = QRTZ_

#開啟分散式部署
org.quartz.jobStore.isClustered = true
#配置是否使用
org.quartz.jobStore.useProperties = false

#分散式節點有效性檢查時間間隔,單位:毫秒
org.quartz.jobStore.clusterCheckinInterval = 20000

#執行緒池實現類
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

#執行最大併發執行緒數量
org.quartz.threadPool.threadCount = 10

#執行緒優先順序
org.quartz.threadPool.threadPriority = 5

#配置為守護執行緒,設定後任務將不會執行
#org.quartz.threadPool.makeThreadsDaemons=true

#配置是否啟動自動載入資料庫內的定時任務,預設true
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true複製程式碼

由於我們下一章需要做分散式多節點自動交付高可用,本章的配置檔案加入了分散式相關的配置。
在上面配置中org.quartz.jobStore.classorg.quartz.jobStore.driverDelegateClass是定時任務持久化的關鍵配置,配置了資料庫持久化定時任務以及採用MySQL資料庫進行連線,當然這裡我們也可以配置其他的資料庫,如下所示:
PostgreSQLorg.quartz.impl.jdbcjobstore.PostgreSQLDelegate
Sybase : org.quartz.impl.jdbcjobstore.SybaseDelegate
MSSQL : org.quartz.impl.jdbcjobstore.MSSQLDelegate
HSQLDB : org.quartz.impl.jdbcjobstore.HSQLDBDelegate
Oracle : org.quartz.impl.jdbcjobstore.oracle.OracleDelegate

org.quartz.jobStore.tablePrefix屬性配置了定時任務資料表的字首,在quartz官方提供的建立表SQL指令碼預設就是qrtz_,在對應的XxxDelegate驅動類內也是使用的預設值,所以這裡我們如果修改表名字首,配置可以去掉。

org.quartz.jobStore.isClustered屬性配置了開啟定時任務分散式功能,再開啟分散式時對應屬性org.quartz.scheduler.instanceId 改成Auto配置即可,例項唯一標識會自動生成,這個標識具體生成的內容,我們一會在執行的控制檯就可以看到了,定時任務分散式準備好後會輸出相關的分散式節點配置資訊。

建立表SQL會在本章原始碼resources目錄下,原始碼地址gitee.com/hengboy/spr…

準備測試

我們先來建立一個簡單的商品資料表,建表SQL如下所示:

DROP TABLE IF EXISTS `basic_good_info`;
CREATE TABLE `basic_good_info` (
  `BGI_ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品編號',
  `BGI_NAME` varchar(20) DEFAULT NULL COMMENT '商品名稱',
  `BGI_PRICE` decimal(8,2) DEFAULT NULL COMMENT '單價',
  `BGI_UNIT` varchar(10) DEFAULT NULL COMMENT '單位',
  PRIMARY KEY (`BGI_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COMMENT='商品基本資訊';複製程式碼

GoodEntity

我們先來針對表basic_good_info建立一個實體,並且新增JPA相關的配置,如下所示:

package com.hengyu.chapter39.good.entity;

import lombok.Data;

import javax.persistence.*;
import java.math.BigDecimal;

/**
 * ========================
 *
 * @author 恆宇少年
 * Created with IntelliJ IDEA.
 * Date:2017/11/5
 * Time:14:59
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@Entity
@Table(name = "basic_good_info")
@Data
public class GoodInfoEntity
{
    /**
     * 商品編號
     */
    @Id
    @GeneratedValue
    @Column(name = "bgi_id")
    private Long id;
    /**
     * 商品名稱
     */
    @Column(name = "bgi_name")
    private String name;
    /**
     * 商品單位
     */
    @Column(name = "bgi_unit")
    private String unit;
    /**
     * 商品單價
     */
    @Column(name = "bgi_price")
    private BigDecimal price;
}複製程式碼

下面我們根據商品實體來建立JPA介面,如下所示:

/**
 * ========================
 * Created with IntelliJ IDEA.
 * Date:2017/11/5
 * Time:14:55
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 * @author 恆宇少年
 */
public interface GoodInfoRepository
    extends JpaRepository<GoodInfoEntity,Long>
{
}複製程式碼

接下來我們再來新增一個商品新增的控制器方法,如下所示:

/**
 * ========================
 *
 * @author 恆宇少年
 * Created with IntelliJ IDEA.
 * Date:2017/11/5
 * Time:15:02
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@RestController
@RequestMapping(value = "/good")
public class GoodController
{
    /**
     * 商品業務邏輯實現
     */
    @Autowired
    private GoodInfoService goodInfoService;
    /**
     * 新增商品
     * @return
     */
    @RequestMapping(value = "/save")
    public Long save(GoodInfoEntity good) throws Exception
    {
        return goodInfoService.saveGood(good);
    }
}複製程式碼

在請求商品新增方法時,我們呼叫了GoodInfoService內的saveGood方法,傳遞一個商品的例項作為引數。我們接下來看看該類內相關程式碼,如下所示:

/**
 * 商品業務邏輯
 * ========================
 *
 * @author 恆宇少年
 * Created with IntelliJ IDEA.
 * Date:2017/11/5
 * Time:15:04
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class GoodInfoService
{
    /**
     * 注入任務排程器
     */
    @Autowired
    private Scheduler scheduler;
    /**
     * 商品資料介面
     */
    @Autowired
    private GoodInfoRepository goodInfoRepository;

    /**
     * 儲存商品基本資訊
     * @param good 商品例項
     * @return
     */
    public Long saveGood(GoodInfoEntity good) throws Exception
    {
        goodInfoRepository.save(good);
        return good.getId();
    }複製程式碼

我們只是作為儲存商品的操作,下面我們來模擬一個需求,在商品新增完成後1分鐘我們通知後續的邏輯進行下一步處理,同時開始商品庫存定時檢查的任務。

定義商品新增定時任務

我們先來建立一個任務例項,並且繼承org.springframework.scheduling.quartz.QuartzJobBean抽象類,重寫父抽象類內的executeInternal方法來實現任務的主體邏輯。如下所示:

/**
 * 商品新增定時任務實現類
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/11/5
 * Time:14:47
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 * @author 恆宇少年
 */
public class GoodAddTimer
    extends QuartzJobBean
{
    /**
     * logback
     */
    static Logger logger = LoggerFactory.getLogger(GoodAddTimer.class);
    /**
     * 定時任務邏輯實現方法
     * 每當觸發器觸發時會執行該方法邏輯
     * @param jobExecutionContext 任務執行上下文
     * @throws JobExecutionException
     */
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        logger.info("商品新增完成後執行任務,任務時間:{}",new Date());
    }複製程式碼

在任務主體邏輯內,我們只是做了一個簡單的輸出任務執行的時間,下面我們再來建立庫存定時檢查任務。

定義商品庫存檢查任務

同樣需要繼承org.springframework.scheduling.quartz.QuartzJobBean抽象類實現抽象類內的executeInternal方法,如下所示:

/**
 * 商品庫存檢查定時任務
 * ========================
 *
 * @author 恆宇少年
 * Created with IntelliJ IDEA.
 * Date:2017/11/5
 * Time:15:47
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
public class GoodStockCheckTimer
    extends QuartzJobBean
{
    /**
     * logback
     */
    static Logger logger = LoggerFactory.getLogger(GoodStockCheckTimer.class);

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        logger.info("執行庫存檢查定時任務,執行時間:{}",new Date());
    }
}複製程式碼

都是簡單的做了下日誌的輸出,下面我們需要重構GoodInfoService內的saveGood方法,對應的新增上面兩個任務的建立。

設定商品新增任務到排程器

GoodInfoService類內新增buildCreateGoodTimer方法用於例項化商品新增任務,如下所示:

/**
     * 構建建立商品定時任務
     */
    public void buildCreateGoodTimer() throws Exception
    {
        //設定開始時間為1分鐘後
        long startAtTime = System.currentTimeMillis() + 1000 * 60;
        //任務名稱
        String name = UUID.randomUUID().toString();
        //任務所屬分組
        String group = GoodAddTimer.class.getName();
        //建立任務
        JobDetail jobDetail = JobBuilder.newJob(GoodAddTimer.class).withIdentity(name,group).build();
        //建立任務觸發器
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity(name,group).startAt(new Date(startAtTime)).build();
        //將觸發器與任務繫結到排程器內
        scheduler.scheduleJob(jobDetail, trigger);
    }複製程式碼

在上面方法中我們定義的GoodAddTimer例項只執行一次,在商品新增完成後延遲1分鐘進行呼叫任務主體邏輯。

其中任務的名稱以及任務的分組是為了區分任務做的限制,在同一個分組下如果加入同樣名稱的任務,則會提示任務已經存在,新增失敗的提示。

我們通過JobDetail來構建一個任務例項,設定GoodAddTimer類作為任務執行目標物件,當任務被觸發時就會執行GoodAddTimer內的executeInternal方法。

一個任務需要設定對應的觸發器,觸發器也分為很多種,該任務中我們並沒有採用cron表示式來設定觸發器,而是呼叫startAt方法設定任務開始執行時間。

最後將任務以及任務的觸發器共同交付給任務排程器,這樣就完成了一個任務的設定。

設定商品庫存檢查到任務排程器

GoodInfoService類內新增buildGoodStockTimer方法用於例項化商品新增任務,如下所示:

/**
     * 構建商品庫存定時任務
     * @throws Exception
     */
    public void buildGoodStockTimer() throws Exception
    {
        //任務名稱
        String name = UUID.randomUUID().toString();
        //任務所屬分組
        String group = GoodStockCheckTimer.class.getName();

        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/30 * * * * ?");
        //建立任務
        JobDetail jobDetail = JobBuilder.newJob(GoodStockCheckTimer.class).withIdentity(name,group).build();
        //建立任務觸發器
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity(name,group).withSchedule(scheduleBuilder).build();
        //將觸發器與任務繫結到排程器內
        scheduler.scheduleJob(jobDetail, trigger);
    }複製程式碼

該任務的觸發器我們採用了cron表示式來設定,每隔30秒執行一次任務主體邏輯。

任務觸發器在建立時cron表示式可以搭配startAt方法來同時使用。

下面我們修改GoodInfoService內的saveGood方法,分別呼叫設定任務的兩個方法,如下所示:

/**
     * 儲存商品基本資訊
     * @param good 商品例項
     * @return
     */
    public Long saveGood(GoodInfoEntity good) throws Exception
    {
        goodInfoRepository.save(good);
        //構建建立商品定時任務
        buildCreateGoodTimer();
        //構建商品庫存定時任務
        buildGoodStockTimer();
        return good.getId();
    }複製程式碼

下面我們就來測試下任務是否可以順序的被持久化到資料庫,並且是否可以在重啟服務後執行重啟前新增的任務。

測試

下面我們來啟動專案,啟動成功後,我們來檢視控制檯輸出的分散式節點的資訊,如下所示:

2017-11-05 18:09:40.052  INFO 7708 --- [           main] c.hengyu.chapter39.Chapter39Application  : 【【【【【【定時任務分散式節點 - 1 已啟動】】】】】】
2017-11-05 18:09:42.005  INFO 7708 --- [lerFactoryBean]] o.s.s.quartz.SchedulerFactoryBean        : Starting Quartz Scheduler now, after delay of 2 seconds
2017-11-05 18:09:42.027  INFO 7708 --- [lerFactoryBean]] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: detected 1 failed or restarted instances.
2017-11-05 18:09:42.027  INFO 7708 --- [lerFactoryBean]] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: Scanning for instance "yuqiyu1509876084785"'s failed in-progress jobs.
2017-11-05 18:09:42.031  INFO 7708 --- [lerFactoryBean]] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: ......Freed 1 acquired trigger(s).
2017-11-05 18:09:42.033  INFO 7708 --- [lerFactoryBean]] org.quartz.core.QuartzScheduler          : Scheduler schedulerFactoryBean_$_yuqiyu1509876579404 started.複製程式碼

定時任務是在專案啟動後2秒進行執行初始化,並且通過ClusterManager來完成了instance的建立,建立的節點唯一標識為yuqiyu1509876084785

編寫商品控制器請求方法測試用例,如下所示:

@RunWith(SpringRunner.class)
@SpringBootTest
public class Chapter39ApplicationTests {
    /**
     * 模擬mvc測試物件
     */
    private MockMvc mockMvc;

    /**
     * web專案上下文
     */
    @Autowired
    private WebApplicationContext webApplicationContext;

    /**
     * 所有測試方法執行之前執行該方法
     */
    @Before
    public void before() {
        //獲取mockmvc物件例項
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

    /**
     * 測試新增商品
     * @throws Exception
     */
    @Test
    public void addGood() throws Exception
    {
        MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/good/save")
                .param("name","西瓜")
                .param("unit","斤")
                .param("price","12.88")
        )
                .andDo(MockMvcResultHandlers.print())
                .andExpect(MockMvcResultMatchers.status().is(200))
                .andReturn();
        result.getResponse().setCharacterEncoding("UTF-8");
        System.out.println(result.getResponse().getContentAsString());
    }複製程式碼

測試用例相關文章請訪問第三十五章:SpringBoot與單元測試的小祕密,我們來執行addGood測試方法,檢視控制檯輸出,如下所示:

....省略部分輸出
Hibernate: insert into basic_good_info (bgi_name, bgi_price, bgi_unit) values (?, ?, ?)
2017-11-05 18:06:35.699 TRACE 7560 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [西瓜]
2017-11-05 18:06:35.701 TRACE 7560 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [NUMERIC] - [12.88]
2017-11-05 18:06:35.701 TRACE 7560 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [斤]
....省略部分輸出
8
....省略部分輸出複製程式碼

可以看到我們的商品已被成功的寫入到資料庫並且輸出的主鍵值,我們的任務是否也成功的被寫入到資料庫了呢?我們來檢視qrtz_job_details表內任務列表,如下所示:

schedulerFactoryBean    7567c9d7-76f5-47f3-bc5d-b934f4c1063b    com.hengyu.chapter39.timers.GoodStockCheckTimer        com.hengyu.chapter39.timers.GoodStockCheckTimer    0    0    0    0    0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787000737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F40000000000010770800000010000000007800
schedulerFactoryBean    e5e08ab0-9be3-43fb-93b8-b9490432a5d7    com.hengyu.chapter39.timers.GoodAddTimer        com.hengyu.chapter39.timers.GoodAddTimer    0    0    0    0    0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787000737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F40000000000010770800000010000000007800複製程式碼

任務已經被成功的持久化到資料庫內,等待1分鐘後檢視控制檯輸出內容如下所示:

2017-11-05 18:12:30.017  INFO 7708 --- [ryBean_Worker-1] c.h.c.timers.GoodStockCheckTimer         : 執行庫存檢查定時任務,執行時間:Sun Nov 05 18:12:30 CST 2017
2017-11-05 18:13:00.009  INFO 7708 --- [ryBean_Worker-2] c.h.c.timers.GoodStockCheckTimer         : 執行庫存檢查定時任務,執行時間:Sun Nov 05 18:13:00 CST 2017
2017-11-05 18:13:02.090  INFO 7708 --- [ryBean_Worker-3] c.hengyu.chapter39.timers.GoodAddTimer   : 商品新增完成後執行任務,任務時間:Sun Nov 05 18:13:02 CST 2017複製程式碼

根據輸出的內容來判定完全吻合我們的配置引數,庫存檢查為30秒執行一次,而新增成功後的提醒則是1分鐘後執行一次。執行完成後就會被直接銷燬,我們再來檢視資料庫表qrtz_job_details,這時就可以看到還剩下1個任務

重啟服務任務是否自動執行?

下面我們把專案重啟下,然後觀察控制檯的輸出內容,如下所示:

2017-11-05 18:15:54.018  INFO 7536 --- [           main] c.hengyu.chapter39.Chapter39Application  : 【【【【【【定時任務分散式節點 - 1 已啟動】】】】】】
2017-11-05 18:15:55.975  INFO 7536 --- [lerFactoryBean]] o.s.s.quartz.SchedulerFactoryBean        : Starting Quartz Scheduler now, after delay of 2 seconds
2017-11-05 18:15:56.000  INFO 7536 --- [lerFactoryBean]] org.quartz.core.QuartzScheduler          : Scheduler schedulerFactoryBean_$_yuqiyu1509876953202 started.
2017-11-05 18:16:15.999  INFO 7536 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: detected 1 failed or restarted instances.
2017-11-05 18:16:16.000  INFO 7536 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: Scanning for instance "yuqiyu1509876579404"'s failed in-progress jobs.
2017-11-05 18:16:16.005  INFO 7536 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: ......Freed 1 acquired trigger(s).
2017-11-05 18:16:16.041  INFO 7536 --- [ryBean_Worker-1] c.h.c.timers.GoodStockCheckTimer         : 執行庫存檢查定時任務,執行時間:Sun Nov 05 18:16:16 CST 2017複製程式碼

可以看到成功的自動執行了我們在重啟之前配置的任務。

總結

本章主要講解了SpringBoot整合quartz定時任務框架,完成了分散式單節點任務持久化,下一章我們會講解任務引數傳遞以及分散式多節點任務自動負載。

本章原始碼已經上傳到碼雲:
SpringBoot配套原始碼地址:gitee.com/hengboy/spr…
SpringCloud配套原始碼地址:gitee.com/hengboy/spr…
SpringBoot相關係列文章請訪問:目錄:SpringBoot學習目錄
QueryDSL相關係列文章請訪問:QueryDSL通用查詢框架學習目錄
SpringDataJPA相關係列文章請訪問:目錄:SpringDataJPA學習目錄
SpringBoot相關文章請訪問:目錄:SpringBoot學習目錄,感謝閱讀!
歡迎加入QQ技術交流群,共同進步。

QQ技術交流群

相關文章