springBoot整合quartz数据库管理定时任务样例demo

样例介绍

采用quartz,并使用数据库来动态管理定时任务。

步骤

(1)导入依赖

<!--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>
<!--数据库 -->
<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)添加配置

数据库配置:

spring.datasource.druid.url=jdbc:mysql://*.*.*.*(这里使用自己服务器的ip):3306/(自己的数据库)?reWriteBatchedInserts=true
spring.datasource.druid.username=root
spring.datasource.druid.password=123456
spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver

quartz配置:

#--------------------------------------------------------------
#调度器属性
#--------------------------------------------------------------
# 调度器实例名:可以随意命名
org.quartz.scheduler.instanceName=quartz-demo
# 实例ID: 允许随意命名,但必须保持唯一,集群可以让quartz来帮你命名,设置为AUTO
org.quartz.scheduler.instanceId=AUTO
#只有org.quartz.scheduler.instanceId设置为“AUTO”才使用。
# 默认为“org.quartz.simpl.SimpleInstanceIdGenerator”,它是主机名和时间戳生成实例Id的。
# 其它的IntanceIdGenerator实现包括SystemPropertyInstanceIdGenerator(它从系统属性“org.quartz.scheduler.instanceId”获取实例Id),
# 和HostnameInstanceIdGenerator(它使用本地主机名InetAddress.getLocalHost().getHostName()生成实例Id)。
# 你也实现你自己的InstanceIdGenerator
org.quartz.scheduler.instanceIdGenerator.class=com.yw.quartzdemo.support.QuartzInstanceIdGenerator
#--------------------------------------------------------------
# 线程池属性
#--------------------------------------------------------------
#  org.quartz.threadPool.class这个值是一个实现了 org.quartz.spi.ThreadPool 接口的类的全限名称。
#  Quartz 自带的线程池实现类是 org.quartz.smpl.SimpleThreadPool,
#  它能够满足大多数用户的需求。这个线程池实现具备简单的行为,并经很好的测试过。
#  它在调度器的生命周期中提供固定大小的线程池。
#  你能根据需求创建自己的线程池实现,如果你想要一个随需可伸缩的线程池时也许需要这么做。
#  这个属性没有默认值,你必须为其指定值。
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
# threadCount 属性控制了多少个工作者线程被创建用来处理 Job。
# 原则上是,要处理的 Job 越多,那么需要的工作者线程也就越多。
# threadCount 的数值至少为 1。
# Quartz 没有限定你设置工作者线程的最大值,但是在多数机器上设置该值超过100的话就会显得相当不实用了,
# 特别是在你的 Job 执行时间较长的情况下。
# 这项没有默认值,所以你必须为这个属性设定一个值
org.quartz.threadPool.threadCount=10
# 优先级:可以是Thread.MIN_PRIORITY (1)和Thread.MAX_PRIORITY (10)之间的任意整数。默认为Thread.NORM_PRIORITY (5).
org.quartz.threadPool.threadPriority=5
# 继承初始化线程的上下文类加载器线程
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
#--------------------------------------------------------------
#作业存储设置:作业存储部分的设置描述了在调度器实例的生命周期中,Job 和 Trigger 信息是如何被存储的。
# 把调度器信息存储在内存中非常的快也易于配置。
# 当调度器进程一旦被终止,所有的 Job 和 Trigger 的状态就丢失了。
# 要使 Job 存储在内存中需通过设置 org.quartz.jobStrore.class 属性为 org.quartz.simpl.RAMJobStore,
# 在Cron Trigger 和“作业存储和持久化”会用到的不同类型的作业存储实现。
#--------------------------------------------------------------
# trigger被认为失败之前,scheduler能够承受的下一次触发时间(单位毫秒)。默认值为60秒。
org.quartz.jobStore.misfireThreshold=60000
# 用于将调度信息(job、trigger和calendar)存储到关系数据库中。
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
# 设置当前数据库Driver代理的数据库系统的方言
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(多于几个)回引发数据表长时间锁定,触发其它的trigger(还没有失败)的性能就会受到限制
org.quartz.jobStore.maxMisfiresToHandleAtATime=10
# 使用集群特性,这个属性必须为true。
# 如果你有多个Quartz实例使用相同的数据库表,这个属性必须为true,否则你会体验一把大破坏。参见集群配置。
org.quartz.jobStore.isClustered=true  
# 设置当前实例check in集群中其它实例的频率。影响检测到故障实例的速度
org.quartz.jobStore.clusterCheckinInterval=20000
# “org.quartz.jobStore.useProperties”配置参数可以被设置为true(默认为false),
# 这样可以指导JDBCJobStore,JobDataMaps中的值都是字符串,因此这样可以以名字-值对存储,
# 而不是存储更加复杂的对象(序列化形式BLOB)。
# 从长远来看,这是很安全的,因为避免了将非字符串类序列化为BLOB的类版本问题
org.quartz.jobStore.useProperties=true

(3)添加定时任务表的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 {
    /**
     * 任务名称-类全名
     */
    private String jobName;

    /**
     * 任务分组
     */
    private String jobGroup;

    /**
     * 任务执行表达式
     */
    private String cron;

    /**
     * 任务状态
     */
    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;

/**
 * 任务定义接口
 */
public interface JobDefDAO {

    /***
     * @Description 查询所有任务
     * @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;

/**
 * 任务定义实现
 */
@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("查询定时任务定义异常:{},{}", e.getMessage(), e);
            throw new RuntimeException("查询定时任务定义失败");
        }
        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("定时任务调度实体构建异常");
        }
        return schedulerFactoryBean;
    }

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

        return schedulerFactoryBean.getScheduler();
    }
}

supper包下:

JobRefresh.java 用于设置定时刷新频率。

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 // 应用启动时刷新一次,方便测试,后续可以去掉
    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) {
                // 状态(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("刷新任务异常");
                }
                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)添加定时任务

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)在数据库中添加(5)中任务的触发时间

其中status中0为开启,1为关闭。

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容