定時任務(wù)在企業(yè)項目比較常用到,幾乎所有的項目都會牽扯該功能模塊,定時任務(wù)一般會處理指定時間點(diǎn)執(zhí)行某一些業(yè)務(wù)邏輯、間隔時間執(zhí)行某一些業(yè)務(wù)邏輯等。我們在之前有講過SpringBoot
是已經(jīng)集成了定時任務(wù)的,詳見:第二十六章:SpringBoot使用@Scheduled創(chuàng)建定時任務(wù),那么我們本章將會采用外置的quartz
定時任務(wù)框架來完成定時任務(wù)的分布式單節(jié)點(diǎn)持久化,我們?yōu)槭裁匆志没〞r任務(wù)呢?
在一些項目中定時任務(wù)可能是必不可少的,由于某種特殊的原因定時任務(wù)可能丟失,如重啟定時任務(wù)服務(wù)項目后,原內(nèi)存中的定時任務(wù)就會被完全釋放!那對于我們來說可能是致命的問題。當(dāng)然也有強(qiáng)制的辦法解決這類問題,但是如果我們把定時任務(wù)持久化到數(shù)據(jù)庫,像維護(hù)普通邏輯數(shù)據(jù)那樣維護(hù)任務(wù),就會避免項目中遇到的種種的特殊情況。
免費(fèi)教程專題
恒宇少年在博客整理三套免費(fèi)學(xué)習(xí)教程專題
,由于文章偏多
特意添加了閱讀指南
,新文章以及之前的文章都會在專題內(nèi)陸續(xù)填充
,希望可以幫助大家解惑更多知識點(diǎn)。
本章目標(biāo)
基于SpringBoot
架構(gòu)整合定時任務(wù)框架quartz
來完成分布式單節(jié)點(diǎn)定時任務(wù)持久化,將任務(wù)持久化到數(shù)據(jù)庫,更好的預(yù)防任務(wù)丟失。
SpringBoot 企業(yè)級核心技術(shù)學(xué)習(xí)專題
專題 | 專題名稱 | 專題描述 |
---|---|---|
001 | Spring Boot 核心技術(shù) | 講解SpringBoot一些企業(yè)級層面的核心組件 |
002 | Spring Boot 核心技術(shù)章節(jié)源碼 | Spring Boot 核心技術(shù)簡書每一篇文章碼云對應(yīng)源碼 |
003 | Spring Cloud 核心技術(shù) | 對Spring Cloud核心技術(shù)全面講解 |
004 | Spring Cloud 核心技術(shù)章節(jié)源碼 | Spring Cloud 核心技術(shù)簡書每一篇文章對應(yīng)源碼 |
005 | QueryDSL 核心技術(shù) | 全面講解QueryDSL核心技術(shù)以及基于SpringBoot整合SpringDataJPA |
006 | SpringDataJPA 核心技術(shù) | 全面講解SpringDataJPA核心技術(shù) |
007 | SpringBoot核心技術(shù)學(xué)習(xí)目錄 | SpringBoot系統(tǒng)的學(xué)習(xí)目錄,敬請關(guān)注點(diǎn)贊?。? |
構(gòu)建項目
我們使用idea
開發(fā)工具創(chuàng)建一個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相關(guān)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--web相關(guān)依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--數(shù)據(jù)庫相關(guān)依賴-->
<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相關(guān)依賴-->
<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>
<!--定時任務(wù)需要依賴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
,新版本的任務(wù)調(diào)度框架做出了很多封裝,使用也變得簡易明了。
創(chuàng)建初始化完成,下面我們來創(chuàng)建定時任務(wù)相關(guān)的Configuration
配置。
QuartzConfiguration
quartz
與Spring
相關(guān)框架的整合方式有很多種,我們今天采用jobDetail
使用Spring Ioc
托管方式來完成整合,我們可以在定時任務(wù)實(shí)例中使用Spring
注入注解完成業(yè)務(wù)邏輯處理,下面我先把全部的配置貼出來再逐步分析,配置類如下所示:
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定時任務(wù)配置
* ========================
* 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
* 實(shí)現(xiàn)任務(wù)實(shí)例化方式
*/
public static class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
@Override
public void setApplicationContext(final ApplicationContext context) {
beanFactory = context.getAutowireCapableBeanFactory();
}
/**
* 將job實(shí)例交給spring ioc托管
* 我們在job實(shí)例實(shí)現(xiàn)類內(nèi)可以直接使用spring注入的調(diào)用被spring ioc管理的實(shí)例
* @param bundle
* @return
* @throws Exception
*/
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
/**
* 將job實(shí)例交付給spring ioc
*/
beanFactory.autowireBean(job);
return job;
}
}
/**
* 配置任務(wù)工廠實(shí)例
* @param applicationContext spring上下文實(shí)例
* @return
*/
@Bean
public JobFactory jobFactory(ApplicationContext applicationContext)
{
/**
* 采用自定義任務(wù)工廠 整合spring實(shí)例來完成構(gòu)建任務(wù)
* see {@link AutowiringSpringBeanJobFactory}
*/
AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
}
/**
* 配置任務(wù)調(diào)度器
* 使用項目數(shù)據(jù)源作為quartz數(shù)據(jù)源
* @param jobFactory 自定義配置任務(wù)工廠
* @param dataSource 數(shù)據(jù)源實(shí)例
* @return
* @throws Exception
*/
@Bean(destroyMethod = "destroy",autowire = Autowire.NO)
public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, DataSource dataSource) throws Exception
{
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
//將spring管理job自定義工廠交由調(diào)度器維護(hù)
schedulerFactoryBean.setJobFactory(jobFactory);
//設(shè)置覆蓋已存在的任務(wù)
schedulerFactoryBean.setOverwriteExistingJobs(true);
//項目啟動完成后,等待2秒后開始執(zhí)行調(diào)度器初始化
schedulerFactoryBean.setStartupDelay(2);
//設(shè)置調(diào)度器自動運(yùn)行
schedulerFactoryBean.setAutoStartup(true);
//設(shè)置數(shù)據(jù)源,使用與項目統(tǒng)一數(shù)據(jù)源
schedulerFactoryBean.setDataSource(dataSource);
//設(shè)置上下文spring bean name
schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext");
//設(shè)置配置文件位置
schedulerFactoryBean.setConfigLocation(new ClassPathResource("/quartz.properties"));
return schedulerFactoryBean;
}
}
AutowiringSpringBeanJobFactory
可以看到上面配置類中,AutowiringSpringBeanJobFactory
我們繼承了SpringBeanJobFactory
類,并且通過實(shí)現(xiàn)ApplicationContextAware
接口獲取ApplicationContext
設(shè)置方法,通過外部實(shí)例化時設(shè)置ApplicationContext
實(shí)例對象,在createJobInstance
方法內(nèi),我們采用AutowireCapableBeanFactory
來托管SpringBeanJobFactory
類中createJobInstance
方法返回的定時任務(wù)實(shí)例,這樣我們就可以在定時任務(wù)類內(nèi)使用Spring Ioc
相關(guān)的注解進(jìn)行注入業(yè)務(wù)邏輯實(shí)例了。
JobFactory
任務(wù)工廠是在本章配置調(diào)度器時所需要的實(shí)例,我們通過jobFactory
方法注入ApplicationContext
實(shí)例,來創(chuàng)建一個AutowiringSpringBeanJobFactory
對象,并且將對象實(shí)例托管到Spring Ioc
容器內(nèi)。
SchedulerFactoryBean
我們本章采用的是項目內(nèi)部數(shù)據(jù)源的方式來設(shè)置調(diào)度器的jobSotre
,官方quartz
有兩種持久化的配置方案。
第一種:采用quartz.properties
配置文件配置獨(dú)立的定時任務(wù)數(shù)據(jù)源,可以與使用項目的數(shù)據(jù)庫完全獨(dú)立。
第二種:采用與創(chuàng)建項目統(tǒng)一個數(shù)據(jù)源,定時任務(wù)持久化相關(guān)的表與業(yè)務(wù)邏輯在同一個數(shù)據(jù)庫內(nèi)。
可以根據(jù)實(shí)際的項目需求采取不同的方案,我們本章主要是通過第二種方案來進(jìn)行講解,在上面配置類中可以看到方法schedulerFactoryBean
內(nèi)自動注入了JobFactory
實(shí)例,也就是我們自定義的AutowiringSpringBeanJobFactory
任務(wù)工廠實(shí)例,另外一個參數(shù)就是DataSource
,在我們引入spring-starter-data-jpa
依賴后會根據(jù)application.yml
文件內(nèi)的數(shù)據(jù)源相關(guān)配置自動實(shí)例化DataSource
實(shí)例,這里直接注入是沒有問題的。
我們通過調(diào)用SchedulerFactoryBean
對象的setConfigLocation
方法來設(shè)置quartz
定時任務(wù)框架的基本配置,配置文件所在位置:resources/quartz.properties
=> classpath:/quartz.properties
下。
注意:quartz.properties配置文件一定要放在
classpath
下,放在別的位置有部分功能不會生效。
下面我們來看下quartz.properties
文件內(nèi)的配置,如下所示:
#調(diào)度器實(shí)例名稱
org.quartz.scheduler.instanceName = quartzScheduler
#調(diào)度器實(shí)例編號自動生成
org.quartz.scheduler.instanceId = AUTO
#持久化方式配置
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#持久化方式配置數(shù)據(jù)驅(qū)動,MySQL數(shù)據(jù)庫
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#quartz相關(guān)數(shù)據(jù)表前綴名
org.quartz.jobStore.tablePrefix = QRTZ_
#開啟分布式部署
org.quartz.jobStore.isClustered = true
#配置是否使用
org.quartz.jobStore.useProperties = false
#分布式節(jié)點(diǎn)有效性檢查時間間隔,單位:毫秒
org.quartz.jobStore.clusterCheckinInterval = 20000
#線程池實(shí)現(xiàn)類
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
#執(zhí)行最大并發(fā)線程數(shù)量
org.quartz.threadPool.threadCount = 10
#線程優(yōu)先級
org.quartz.threadPool.threadPriority = 5
#配置為守護(hù)線程,設(shè)置后任務(wù)將不會執(zhí)行
#org.quartz.threadPool.makeThreadsDaemons=true
#配置是否啟動自動加載數(shù)據(jù)庫內(nèi)的定時任務(wù),默認(rèn)true
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
由于我們下一章需要做分布式多節(jié)點(diǎn)自動交付高可用,本章的配置文件加入了分布式相關(guān)的配置。
在上面配置中org.quartz.jobStore.class
與org.quartz.jobStore.driverDelegateClass
是定時任務(wù)持久化的關(guān)鍵配置,配置了數(shù)據(jù)庫持久化定時任務(wù)以及采用MySQL
數(shù)據(jù)庫進(jìn)行連接,當(dāng)然這里我們也可以配置其他的數(shù)據(jù)庫,如下所示:
PostgreSQL
: org.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
屬性配置了定時任務(wù)數(shù)據(jù)表的前綴,在quartz
官方提供的創(chuàng)建表SQL腳本
默認(rèn)就是qrtz_
,在對應(yīng)的XxxDelegate驅(qū)動類內(nèi)
也是使用的默認(rèn)值,所以這里我們?nèi)绻薷谋砻熬Y,配置可以去掉。
org.quartz.jobStore.isClustered
屬性配置了開啟定時任務(wù)分布式功能,再開啟分布式時對應(yīng)屬性org.quartz.scheduler.instanceId
改成Auto
配置即可,實(shí)例唯一標(biāo)識會自動生成,這個標(biāo)識具體生成的內(nèi)容,我們一會在運(yùn)行的控制臺就可以看到了,定時任務(wù)分布式準(zhǔn)備好后會輸出相關(guān)的分布式節(jié)點(diǎn)配置信息。
創(chuàng)建表SQL會在本章源碼resources
目錄下,源碼地址https://gitee.com/hengboy/spring-boot-chapter。
準(zhǔn)備測試
我們先來創(chuàng)建一個簡單的商品數(shù)據(jù)表,建表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
創(chuàng)建一個實(shí)體,并且添加JPA
相關(guān)的配置,如下所示:
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;
}
下面我們根據(jù)商品實(shí)體來創(chuàng)建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
{
/**
* 商品業(yè)務(wù)邏輯實(shí)現(xiàn)
*/
@Autowired
private GoodInfoService goodInfoService;
/**
* 添加商品
* @return
*/
@RequestMapping(value = "/save")
public Long save(GoodInfoEntity good) throws Exception
{
return goodInfoService.saveGood(good);
}
}
在請求商品添加方法時,我們調(diào)用了GoodInfoService
內(nèi)的saveGood
方法,傳遞一個商品的實(shí)例作為參數(shù)。我們接下來看看該類內(nèi)相關(guān)代碼,如下所示:
/**
* 商品業(yè)務(wù)邏輯
* ========================
*
* @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
{
/**
* 注入任務(wù)調(diào)度器
*/
@Autowired
private Scheduler scheduler;
/**
* 商品數(shù)據(jù)接口
*/
@Autowired
private GoodInfoRepository goodInfoRepository;
/**
* 保存商品基本信息
* @param good 商品實(shí)例
* @return
*/
public Long saveGood(GoodInfoEntity good) throws Exception
{
goodInfoRepository.save(good);
return good.getId();
}
我們只是作為保存商品的操作,下面我們來模擬一個需求,在商品添加完成后1分鐘我們通知后續(xù)的邏輯進(jìn)行下一步處理,同時開始商品庫存定時檢查的任務(wù)。
定義商品添加定時任務(wù)
我們先來創(chuàng)建一個任務(wù)實(shí)例,并且繼承org.springframework.scheduling.quartz.QuartzJobBean
抽象類,重寫父抽象類內(nèi)的executeInternal
方法來實(shí)現(xiàn)任務(wù)的主體邏輯。如下所示:
/**
* 商品添加定時任務(wù)實(shí)現(xiàn)類
* ========================
* 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);
/**
* 定時任務(wù)邏輯實(shí)現(xiàn)方法
* 每當(dāng)觸發(fā)器觸發(fā)時會執(zhí)行該方法邏輯
* @param jobExecutionContext 任務(wù)執(zhí)行上下文
* @throws JobExecutionException
*/
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
logger.info("商品添加完成后執(zhí)行任務(wù),任務(wù)時間:{}",new Date());
}
在任務(wù)主體邏輯內(nèi),我們只是做了一個簡單的輸出任務(wù)執(zhí)行的時間,下面我們再來創(chuàng)建庫存定時檢查任務(wù)。
定義商品庫存檢查任務(wù)
同樣需要繼承org.springframework.scheduling.quartz.QuartzJobBean
抽象類實(shí)現(xiàn)抽象類內(nèi)的executeInternal
方法,如下所示:
/**
* 商品庫存檢查定時任務(wù)
* ========================
*
* @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("執(zhí)行庫存檢查定時任務(wù),執(zhí)行時間:{}",new Date());
}
}
都是簡單的做了下日志的輸出,下面我們需要重構(gòu)GoodInfoService
內(nèi)的saveGood
方法,對應(yīng)的添加上面兩個任務(wù)的創(chuàng)建。
設(shè)置商品添加任務(wù)到調(diào)度器
在GoodInfoService
類內(nèi)添加buildCreateGoodTimer
方法用于實(shí)例化商品添加任務(wù),如下所示:
/**
* 構(gòu)建創(chuàng)建商品定時任務(wù)
*/
public void buildCreateGoodTimer() throws Exception
{
//設(shè)置開始時間為1分鐘后
long startAtTime = System.currentTimeMillis() + 1000 * 60;
//任務(wù)名稱
String name = UUID.randomUUID().toString();
//任務(wù)所屬分組
String group = GoodAddTimer.class.getName();
//創(chuàng)建任務(wù)
JobDetail jobDetail = JobBuilder.newJob(GoodAddTimer.class).withIdentity(name,group).build();
//創(chuàng)建任務(wù)觸發(fā)器
Trigger trigger = TriggerBuilder.newTrigger().withIdentity(name,group).startAt(new Date(startAtTime)).build();
//將觸發(fā)器與任務(wù)綁定到調(diào)度器內(nèi)
scheduler.scheduleJob(jobDetail, trigger);
}
在上面方法中我們定義的GoodAddTimer
實(shí)例只運(yùn)行一次,在商品添加完成后延遲1分鐘進(jìn)行調(diào)用任務(wù)主體邏輯。
其中任務(wù)的名稱以及任務(wù)的分組是為了區(qū)分任務(wù)做的限制,在同一個分組下如果加入同樣名稱的任務(wù),則會提示任務(wù)已經(jīng)存在,添加失敗的提示。
我們通過JobDetail
來構(gòu)建一個任務(wù)實(shí)例,設(shè)置GoodAddTimer
類作為任務(wù)運(yùn)行目標(biāo)對象,當(dāng)任務(wù)被觸發(fā)時就會執(zhí)行GoodAddTimer
內(nèi)的executeInternal
方法。
一個任務(wù)需要設(shè)置對應(yīng)的觸發(fā)器,觸發(fā)器也分為很多種,該任務(wù)中我們并沒有采用cron
表達(dá)式來設(shè)置觸發(fā)器,而是調(diào)用startAt
方法設(shè)置任務(wù)開始執(zhí)行時間。
最后將任務(wù)以及任務(wù)的觸發(fā)器共同交付給任務(wù)調(diào)度器,這樣就完成了一個任務(wù)的設(shè)置。
設(shè)置商品庫存檢查到任務(wù)調(diào)度器
在GoodInfoService
類內(nèi)添加buildGoodStockTimer
方法用于實(shí)例化商品添加任務(wù),如下所示:
/**
* 構(gòu)建商品庫存定時任務(wù)
* @throws Exception
*/
public void buildGoodStockTimer() throws Exception
{
//任務(wù)名稱
String name = UUID.randomUUID().toString();
//任務(wù)所屬分組
String group = GoodStockCheckTimer.class.getName();
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/30 * * * * ?");
//創(chuàng)建任務(wù)
JobDetail jobDetail = JobBuilder.newJob(GoodStockCheckTimer.class).withIdentity(name,group).build();
//創(chuàng)建任務(wù)觸發(fā)器
Trigger trigger = TriggerBuilder.newTrigger().withIdentity(name,group).withSchedule(scheduleBuilder).build();
//將觸發(fā)器與任務(wù)綁定到調(diào)度器內(nèi)
scheduler.scheduleJob(jobDetail, trigger);
}
該任務(wù)的觸發(fā)器我們采用了cron
表達(dá)式來設(shè)置,每隔30秒執(zhí)行一次任務(wù)主體邏輯。
任務(wù)觸發(fā)器在創(chuàng)建時
cron
表達(dá)式可以搭配startAt
方法來同時使用。
下面我們修改GoodInfoService
內(nèi)的saveGood
方法,分別調(diào)用設(shè)置任務(wù)的兩個方法,如下所示:
/**
* 保存商品基本信息
* @param good 商品實(shí)例
* @return
*/
public Long saveGood(GoodInfoEntity good) throws Exception
{
goodInfoRepository.save(good);
//構(gòu)建創(chuàng)建商品定時任務(wù)
buildCreateGoodTimer();
//構(gòu)建商品庫存定時任務(wù)
buildGoodStockTimer();
return good.getId();
}
下面我們就來測試下任務(wù)是否可以順序的被持久化到數(shù)據(jù)庫,并且是否可以在重啟服務(wù)后執(zhí)行重啟前添加的任務(wù)。
測試
下面我們來啟動項目,啟動成功后
,我們來查看控制臺輸出的分布式節(jié)點(diǎn)的信息,如下所示:
2017-11-05 18:09:40.052 INFO 7708 --- [ main] c.hengyu.chapter39.Chapter39Application : 【【【【【【定時任務(wù)分布式節(jié)點(diǎn) - 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.
定時任務(wù)是在項目啟動后2秒進(jìn)行執(zhí)行初始化,并且通過ClusterManager
來完成了instance
的創(chuàng)建,創(chuàng)建的節(jié)點(diǎn)唯一標(biāo)識為yuqiyu1509876084785
。
編寫商品控制器請求方法測試用例,如下所示:
@RunWith(SpringRunner.class)
@SpringBootTest
public class Chapter39ApplicationTests {
/**
* 模擬mvc測試對象
*/
private MockMvc mockMvc;
/**
* web項目上下文
*/
@Autowired
private WebApplicationContext webApplicationContext;
/**
* 所有測試方法執(zhí)行之前執(zhí)行該方法
*/
@Before
public void before() {
//獲取mockmvc對象實(shí)例
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());
}
測試用例相關(guān)文章請訪問第三十五章:SpringBoot與單元測試的小秘密,我們來執(zhí)行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
....省略部分輸出
可以看到我們的商品已被成功的寫入到數(shù)據(jù)庫并且輸出的主鍵值,我們的任務(wù)是否也成功的被寫入到數(shù)據(jù)庫了呢?我們來查看qrtz_job_details
表內(nèi)任務(wù)列表,如下所示:
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
任務(wù)已經(jīng)被成功的持久化到數(shù)據(jù)庫內(nèi),等待1分鐘后查看控制臺輸出內(nèi)容如下所示:
2017-11-05 18:12:30.017 INFO 7708 --- [ryBean_Worker-1] c.h.c.timers.GoodStockCheckTimer : 執(zhí)行庫存檢查定時任務(wù),執(zhí)行時間: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 : 執(zhí)行庫存檢查定時任務(wù),執(zhí)行時間: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 : 商品添加完成后執(zhí)行任務(wù),任務(wù)時間:Sun Nov 05 18:13:02 CST 2017
根據(jù)輸出的內(nèi)容來判定完全吻合我們的配置參數(shù),庫存檢查為30秒執(zhí)行一次,而添加成功后的提醒則是1分鐘后執(zhí)行一次。執(zhí)行完成后就會被直接銷毀,我們再來查看數(shù)據(jù)庫表qrtz_job_details
,這時就可以看到還剩下1個任務(wù)
。
重啟服務(wù)任務(wù)是否自動執(zhí)行?
下面我們把項目重啟下,然后觀察控制臺的輸出內(nèi)容,如下所示:
2017-11-05 18:15:54.018 INFO 7536 --- [ main] c.hengyu.chapter39.Chapter39Application : 【【【【【【定時任務(wù)分布式節(jié)點(diǎn) - 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 : 執(zhí)行庫存檢查定時任務(wù),執(zhí)行時間:Sun Nov 05 18:16:16 CST 2017
可以看到成功的自動執(zhí)行了我們在重啟之前配置的任務(wù)。
總結(jié)
本章主要講解了SpringBoot
整合quartz
定時任務(wù)框架,完成了分布式單節(jié)點(diǎn)任務(wù)持久化,下一章我們會講解任務(wù)參數(shù)傳遞以及分布式多節(jié)點(diǎn)任務(wù)自動負(fù)載。
本章源碼已經(jīng)上傳到碼云:
SpringBoot配套源碼地址:https://gitee.com/hengboy/spring-boot-chapter
SpringCloud配套源碼地址:https://gitee.com/hengboy/spring-cloud-chapter