springBoot整合quartz數(shù)據(jù)庫管理定時任務(wù)樣例demo

樣例介紹

采用quartz,并使用數(shù)據(jù)庫來動態(tài)管理定時任務(wù)月幌。

步驟

(1)導(dǎo)入依賴

<!--quartz -->
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.0</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
</dependency>
<!--數(shù)據(jù)庫 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.0.5</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>

(2)添加配置

數(shù)據(jù)庫配置:

spring.datasource.druid.url=jdbc:mysql://*.*.*.*(這里使用自己服務(wù)器的ip):3306/(自己的數(shù)據(jù)庫)?reWriteBatchedInserts=true
spring.datasource.druid.username=root
spring.datasource.druid.password=123456
spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver

quartz配置:

#--------------------------------------------------------------
#調(diào)度器屬性
#--------------------------------------------------------------
# 調(diào)度器實例名:可以隨意命名
org.quartz.scheduler.instanceName=quartz-demo
# 實例ID: 允許隨意命名策菜,但必須保持唯一,集群可以讓quartz來幫你命名,設(shè)置為AUTO
org.quartz.scheduler.instanceId=AUTO
#只有org.quartz.scheduler.instanceId設(shè)置為“AUTO”才使用剖踊。
# 默認為“org.quartz.simpl.SimpleInstanceIdGenerator”驼鞭,它是主機名和時間戳生成實例Id的秦驯。
# 其它的IntanceIdGenerator實現(xiàn)包括SystemPropertyInstanceIdGenerator(它從系統(tǒng)屬性“org.quartz.scheduler.instanceId”獲取實例Id),
# 和HostnameInstanceIdGenerator(它使用本地主機名InetAddress.getLocalHost().getHostName()生成實例Id)挣棕。
# 你也實現(xiàn)你自己的InstanceIdGenerator
org.quartz.scheduler.instanceIdGenerator.class=com.yw.quartzdemo.support.QuartzInstanceIdGenerator
#--------------------------------------------------------------
# 線程池屬性
#--------------------------------------------------------------
#  org.quartz.threadPool.class這個值是一個實現(xiàn)了 org.quartz.spi.ThreadPool 接口的類的全限名稱译隘。
#  Quartz 自帶的線程池實現(xiàn)類是 org.quartz.smpl.SimpleThreadPool,
#  它能夠滿足大多數(shù)用戶的需求洛心。這個線程池實現(xiàn)具備簡單的行為固耘,并經(jīng)很好的測試過。
#  它在調(diào)度器的生命周期中提供固定大小的線程池词身。
#  你能根據(jù)需求創(chuàng)建自己的線程池實現(xiàn)厅目,如果你想要一個隨需可伸縮的線程池時也許需要這么做。
#  這個屬性沒有默認值法严,你必須為其指定值损敷。
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
# threadCount 屬性控制了多少個工作者線程被創(chuàng)建用來處理 Job。
# 原則上是深啤,要處理的 Job 越多拗馒,那么需要的工作者線程也就越多。
# threadCount 的數(shù)值至少為 1墓塌。
# Quartz 沒有限定你設(shè)置工作者線程的最大值瘟忱,但是在多數(shù)機器上設(shè)置該值超過100的話就會顯得相當(dāng)不實用了奥额,
# 特別是在你的 Job 執(zhí)行時間較長的情況下。
# 這項沒有默認值访诱,所以你必須為這個屬性設(shè)定一個值
org.quartz.threadPool.threadCount=10
# 優(yōu)先級:可以是Thread.MIN_PRIORITY (1)和Thread.MAX_PRIORITY (10)之間的任意整數(shù)垫挨。默認為Thread.NORM_PRIORITY (5).
org.quartz.threadPool.threadPriority=5
# 繼承初始化線程的上下文類加載器線程
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
#--------------------------------------------------------------
#作業(yè)存儲設(shè)置:作業(yè)存儲部分的設(shè)置描述了在調(diào)度器實例的生命周期中,Job 和 Trigger 信息是如何被存儲的触菜。
# 把調(diào)度器信息存儲在內(nèi)存中非常的快也易于配置九榔。
# 當(dāng)調(diào)度器進程一旦被終止,所有的 Job 和 Trigger 的狀態(tài)就丟失了涡相。
# 要使 Job 存儲在內(nèi)存中需通過設(shè)置 org.quartz.jobStrore.class 屬性為 org.quartz.simpl.RAMJobStore哲泊,
# 在Cron Trigger 和“作業(yè)存儲和持久化”會用到的不同類型的作業(yè)存儲實現(xiàn)。
#--------------------------------------------------------------
# trigger被認為失敗之前催蝗,scheduler能夠承受的下一次觸發(fā)時間(單位毫秒)切威。默認值為60秒。
org.quartz.jobStore.misfireThreshold=60000
# 用于將調(diào)度信息(job丙号、trigger和calendar)存儲到關(guān)系數(shù)據(jù)庫中先朦。
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
# 設(shè)置當(dāng)前數(shù)據(jù)庫Driver代理的數(shù)據(jù)庫系統(tǒng)的方言
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
# 表前綴
org.quartz.jobStore.tablePrefix=QRTZ_
# JobStore處理失敗trigger的最大等待時間。
# 同時處理多個trigger(多于幾個)回引發(fā)數(shù)據(jù)表長時間鎖定犬缨,觸發(fā)其它的trigger(還沒有失斣骸)的性能就會受到限制
org.quartz.jobStore.maxMisfiresToHandleAtATime=10
# 使用集群特性,這個屬性必須為true怀薛。
# 如果你有多個Quartz實例使用相同的數(shù)據(jù)庫表刺彩,這個屬性必須為true,否則你會體驗一把大破壞枝恋。參見集群配置创倔。
org.quartz.jobStore.isClustered=true  
# 設(shè)置當(dāng)前實例check in集群中其它實例的頻率。影響檢測到故障實例的速度
org.quartz.jobStore.clusterCheckinInterval=20000
# “org.quartz.jobStore.useProperties”配置參數(shù)可以被設(shè)置為true(默認為false)焚碌,
# 這樣可以指導(dǎo)JDBCJobStore三幻,JobDataMaps中的值都是字符串,因此這樣可以以名字-值對存儲呐能,
# 而不是存儲更加復(fù)雜的對象(序列化形式BLOB)。
# 從長遠來看抑堡,這是很安全的摆出,因為避免了將非字符串類序列化為BLOB的類版本問題
org.quartz.jobStore.useProperties=true

(3)添加定時任務(wù)表的PO,mapper,dao

po:

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;


@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("JOB_DEF")
@Builder
public class JobDefPO {
    /**
     * 任務(wù)名稱-類全名
     */
    private String jobName;

    /**
     * 任務(wù)分組
     */
    private String jobGroup;

    /**
     * 任務(wù)執(zhí)行表達式
     */
    private String cron;

    /**
     * 任務(wù)狀態(tài)
     */
    private Integer status;

}

mapper:

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yw.quartzdemo.po.JobDefPO;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface JobDefMapper extends BaseMapper<JobDefPO> {

}

dao

import com.yw.quartzdemo.po.JobDefPO;

import java.util.List;

/**
 * 任務(wù)定義接口
 */
public interface JobDefDAO {

    /***
     * @Description 查詢所有任務(wù)
     * @author yuanwei
     * @param 
     * @return java.util.List<com.yw.quartzdemo.po.JobDefPO>
     * @time 2021/1/6 16:35
     */
    List<JobDefPO> listAll();
}

dao.impl:

import com.baomidou.mybatisplus.core.conditions.Condition;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yw.quartzdemo.dao.JobDefDAO;
import com.yw.quartzdemo.mapper.JobDefMapper;
import com.yw.quartzdemo.po.JobDefPO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 任務(wù)定義實現(xiàn)
 */
@Service
@Slf4j
public class JobDefDAOImpl extends ServiceImpl<JobDefMapper, JobDefPO> implements JobDefDAO {

    
    @Override
    public List<JobDefPO> listAll() {
        List<JobDefPO> result = null;
        final QueryWrapper<JobDefPO> wrapper = Condition.create();
        try {
            result = this.list(wrapper);
        } catch (final Exception e) {
            log.error("查詢定時任務(wù)定義異常:{},{}", e.getMessage(), e);
            throw new RuntimeException("查詢定時任務(wù)定義失敗");
        }
        return result;
    }
}

(4)添加配置:

config包下

quartzConfig.java

import lombok.extern.slf4j.Slf4j;
import org.quartz.Scheduler;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
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.AdaptableJobFactory;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

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

@Configuration
@Slf4j
public class QuartzConfig {

    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;

    @Autowired
    private DataSource dataSource;

    public Properties quartzProperties() throws IOException {

        final PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("quartz.properties"));
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }

    @Bean(name = "quartzJobFactory")
    public AdaptableJobFactory quartzJobFactory() {
        return new AdaptableJobFactory() {
            @Override
            protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {

                final Object jobInstance = super.createJobInstance(bundle);
                capableBeanFactory.autowireBean(jobInstance);
                return jobInstance;
            }
        };
    }

    @Bean(name = "schedulerFactoryBean")
    public SchedulerFactoryBean schedulerFactoryBean(@Qualifier("quartzJobFactory") final AdaptableJobFactory quartzJobFactory) {

        final SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        try {
            schedulerFactoryBean.setQuartzProperties(quartzProperties());
            schedulerFactoryBean.setDataSource(dataSource);
            schedulerFactoryBean.setJobFactory(quartzJobFactory);
            schedulerFactoryBean.setStartupDelay(5);
            schedulerFactoryBean.setOverwriteExistingJobs(true);
        } catch (final IOException ex) {
            log.error(ex.getMessage(), ex);
            throw new RuntimeException("定時任務(wù)調(diào)度實體構(gòu)建異常");
        }
        return schedulerFactoryBean;
    }

    @Bean(name = "scheduler")
    public Scheduler scheduler(@Qualifier("schedulerFactoryBean") final SchedulerFactoryBean schedulerFactoryBean) {

        return schedulerFactoryBean.getScheduler();
    }
}

supper包下:

JobRefresh.java 用于設(shè)置定時刷新頻率首妖。

import com.yw.quartzdemo.dao.JobDefDAO;
import com.yw.quartzdemo.po.JobDefPO;
import lombok.extern.slf4j.Slf4j;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.List;

/**
 * @description
 * @author yuanwei
 * @date 2021/1/6 11:17
 */
@Component
@Slf4j
public class JobRefresh {

    @Autowired
    private JobDefDAO jobDefDAO;

    @Autowired
    private Scheduler scheduler;

    @SuppressWarnings("unchecked")
    @Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1點刷新一次
    @PostConstruct // 應(yīng)用啟動時刷新一次偎漫,方便測試,后續(xù)可以去掉
    public void refreshTrigger() throws SchedulerException {

        final List<JobDefPO> jobDefs = jobDefDAO.listAll();

        for (final JobDefPO jobDef : jobDefs) {
            final TriggerKey triggerKey = TriggerKey.triggerKey(jobDef.getJobName(), jobDef.getJobGroup());
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            if (null == trigger) {
                // 狀態(tài)(0:正常有缆,1:禁用)
                if (jobDef.getStatus().intValue() == 1) {
                    continue;
                }
                JobDetail jobDetail = null;
                try {
                    jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(jobDef.getJobName()))
                            .withIdentity(jobDef.getJobName(), jobDef.getJobGroup()).build();
                } catch (final ClassNotFoundException ex) {
                    log.error(ex.getMessage(), ex);
                    throw new RuntimeException("刷新任務(wù)異常");
                }
                final CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(jobDef.getCron());
                trigger = TriggerBuilder.newTrigger().withIdentity(jobDef.getJobName(), jobDef.getJobGroup())
                        .withSchedule(scheduleBuilder).build();
                scheduler.scheduleJob(jobDetail, trigger);

            } else {
                if (jobDef.getStatus().intValue() == 1) {
                    final JobKey jobKey = JobKey.jobKey(jobDef.getJobName(), jobDef.getJobGroup());
                    scheduler.deleteJob(jobKey);
                    continue;
                }
                final String selectedCron = jobDef.getCron();
                final String currentCron = trigger.getCronExpression();
                if (!selectedCron.equals(currentCron)) {
                    final CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(selectedCron);
                    trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder)
                            .build();
                    scheduler.rescheduleJob(triggerKey, trigger);
                }
            }
        }

    }
}


(5)添加定時任務(wù)

import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.util.Date;

/**
 * @description
 * @author yuanwei
 * @date 2021/1/6 16:38
 */
@Slf4j
public class DemoJobHandler implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        Date time = new Date();
        log.info("時間:{}象踊,打印",time);
    }
}


(6)在數(shù)據(jù)庫中添加(5)中任務(wù)的觸發(fā)時間

其中status中0為開啟温亲,1為關(guān)閉。

INSERT INTO `fund`.`JOB_DEF`(`JOB_NAME`, `JOB_GROUP`, `CRON`, `STATUS`) VALUES ('com.yw.quartzdemo.handler.DemoJobHandler', 'demo', '* * * * * ?', 0);

github鏈接:https://github.com/source201/yw-doc

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末杯矩,一起剝皮案震驚了整個濱河市栈虚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌史隆,老刑警劉巖魂务,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異泌射,居然都是意外死亡粘姜,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門熔酷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來孤紧,“玉大人,你說我怎么就攤上這事拒秘『畔裕” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵翼抠,是天一觀的道長咙轩。 經(jīng)常有香客問我,道長阴颖,這世上最難降的妖魔是什么活喊? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮量愧,結(jié)果婚禮上钾菊,老公的妹妹穿的比我還像新娘。我一直安慰自己偎肃,他們只是感情好煞烫,可當(dāng)我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著累颂,像睡著了一般滞详。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上紊馏,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天料饥,我揣著相機與錄音,去河邊找鬼朱监。 笑死岸啡,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赫编。 我是一名探鬼主播巡蘸,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼奋隶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了悦荒?” 一聲冷哼從身側(cè)響起唯欣,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤痴腌,失蹤者是張志新(化名)和其女友劉穎矗晃,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瓮恭,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡身腻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年产还,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嘀趟。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡脐区,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出她按,到底是詐尸還是另有隱情牛隅,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布酌泰,位于F島的核電站媒佣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏陵刹。R本人自食惡果不足惜默伍,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望衰琐。 院中可真熱鬧也糊,春花似錦、人聲如沸羡宙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狗热。三九已至钞馁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間匿刮,已是汗流浹背指攒。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留僻焚,地道東北人。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓膝擂,卻偏偏與公主長得像虑啤,于是被迫代替她去往敵國和親隙弛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容