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

定時任務在企業項目比較經常使用到,幾乎全部的項目都會牽扯該功能模塊,定時任務通常會處理指定時間點執行某一些業務邏輯、間隔時間執行某一些業務邏輯等。咱們在以前有講過SpringBoot是已經集成了定時任務的,詳見:第二十六章:SpringBoot使用@Scheduled建立定時任務,那麼咱們本章將會採用外置的quartz定時任務框架來完成定時任務的分佈式單節點持久化,咱們爲何要持久化定時任務呢?java

在一些項目中定時任務多是必不可少的,因爲某種特殊的緣由定時任務可能丟失,如重啓定時任務服務項目後,原內存中的定時任務就會被徹底釋放!那對於咱們來講多是致命的問題。固然也有強制的辦法解決這類問題,可是若是咱們把定時任務持久化到數據庫,像維護普通邏輯數據那樣維護任務,就會避免項目中遇到的種種的特殊狀況。mysql

本章目標

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

構建項目

咱們使用idea開發工具建立一個SpringBoot項目,pom.xml依賴配置以下所示:web

...省略部分配置
    <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配置。spring

QuartzConfiguration

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

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容器內。bash

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技術交流羣
相關文章
相關標籤/搜索