Spring Boot 整合 Quartz - 作业调度框架

前言

Quartz 是一个定时任务调度框架,简单易用,功能强大可以使实现定时任务的。在项目开发过程当中,某些定时任务,可能在运行一段时间之后,就不需要了,或者需要修改下定时任务的执行时间等等。需要在代码当中进行修改然后重新打包发布,很麻烦。使用Quartz来实现的话不需要重新修改代码而达到要求。

Quartz 核心概念

  • Job:我们要定时执行某件事情,然后我们把它写成一个 java 类,这就是 Job
  • JobDetail:具体任务类有了,还需要任务详情类( JobDetail )去构建 Job。
  • Trigger 触发器:你把任务放进去了,什么时候执行?这就是触发器的作用。它根据你 cron 表达式的时间去触发任务详情。一个触发器对应一个任务详情。
  • Scheduler调度器:一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。

Quartz 作业存储类型

  • RAMJobStore:RAM 也就是内存,默认情况下 Quartz 会将任务调度存储在内存中,内存的速度是最快的。不好的地方就是数据缺乏持久性,但程序崩溃或者重新发布的时候,所有运行信息都会丢失。
  • JDBC 作业存储:存到数据库之后,可以做单点也可以做集群,当任务多了之后,可以统一进行管理,随时停止、暂停、修改任务。关闭或者重启服务器,运行的信息都不会丢失。缺点就是运行速度快慢取决于连接数据库的快慢。

Quartz 工作原理

Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。

Quartz 的集群模式指的是一个集群下多个节点管理同一批任务的调度,通过共享数据库的方式实现,保证同一个任务到达触发时间的时候,只有一台机器去执行该任务。每个节点部署一个单独的 quartz 实例,相互之间没有直接数据通信,quartz 集群是通过数据库表来感知其他的应用的,各个节点之间并没有直接的通信。只有使用持久的 JobStore 才能完成 Quartz 集群。
在这里插入图片描述

/**
 * @Description: 1.Scheduler 调度程序 scheduler是一个计划调度器容器(总部),容器里面可以盛放众多的JobDetail和trigger,
 * 当容器启动后,里面的每个JobDetail都会根据trigger按部就班自动去执行,scheduler是个容器,
 * 容器中有一个线程池,用来并行调度执行每个作业,这样可以提高容器效率
 *
 * 2.Trigger 触发器 Trigger代表一个调度参数的配置,什么时候去调,当JobDetail和Trigger在scheduler容器上注册后,
 * 形成了装配好的作业(JobDetail和Trigger所组成的一对儿),就可以伴随容器启动而调度执行了
 *
 * 3.Job&JobDetail 工作 Quartz将任务分为Job、JobDetail两部分,其中Job用来定义任务的执行逻辑,
 * 而JobDetail用来描述Job的定义(例如Job接口的实现类以及其他相关的静态信息)。
 *
 *                                  StdSchedulerFactory
 *                                          ↓
 *                                       Scheduler
 *                                        ↓     ↓
 *                          Job ← JobDetail ··· Trigger
 *                                          1:n
 * @Author: yangjj_t
 * @Date: 2021/10/18 15:01
 */

Spring Boot 整合 Quartz 定时调度框架

  1. 引入 quartz 依赖
<!--quartz依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
  1. 数据库建 quartz 相关表
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;

CREATE TABLE `QRTZ_CALENDARS` (
  `SCHED_NAME` VARCHAR(120) NOT NULL,
  `CALENDAR_NAME` VARCHAR(190) NOT NULL,
  `CALENDAR` BLOB NOT NULL,
  PRIMARY KEY (`SCHED_NAME`,`CALENDAR_NAME`)
);

CREATE TABLE `QRTZ_FIRED_TRIGGERS` (
  `SCHED_NAME` VARCHAR(120) NOT NULL,
  `ENTRY_ID` VARCHAR(95) NOT NULL,
  `TRIGGER_NAME` VARCHAR(190) NOT NULL,
  `TRIGGER_GROUP` VARCHAR(190) NOT NULL,
  `INSTANCE_NAME` VARCHAR(190) NOT NULL,
  `FIRED_TIME` BIGINT NOT NULL,
  `SCHED_TIME` BIGINT NOT NULL,
  `PRIORITY` INT NOT NULL,
  `STATE` VARCHAR(16) NOT NULL,
  `JOB_NAME` VARCHAR(190) DEFAULT NULL,
  `JOB_GROUP` VARCHAR(190) DEFAULT NULL,
  `IS_NONCONCURRENT` VARCHAR(1) DEFAULT NULL,
  `REQUESTS_RECOVERY` VARCHAR(1) DEFAULT NULL,
  PRIMARY KEY (`SCHED_NAME`,`ENTRY_ID`),
  KEY `IDX_QRTZ_FT_TRIG_INST_NAME` (`SCHED_NAME`,`INSTANCE_NAME`),
  KEY `IDX_QRTZ_FT_INST_JOB_REQ_RCVRY` (`SCHED_NAME`,`INSTANCE_NAME`,`REQUESTS_RECOVERY`),
  KEY `IDX_QRTZ_FT_J_G` (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`),
  KEY `IDX_QRTZ_FT_JG` (`SCHED_NAME`,`JOB_GROUP`),
  KEY `IDX_QRTZ_FT_T_G` (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
  KEY `IDX_QRTZ_FT_TG` (`SCHED_NAME`,`TRIGGER_GROUP`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `QRTZ_JOB_DETAILS` (
  `SCHED_NAME` VARCHAR(120) NOT NULL,
  `JOB_NAME` VARCHAR(190) NOT NULL,
  `JOB_GROUP` VARCHAR(190) NOT NULL,
  `DESCRIPTION` VARCHAR(250) DEFAULT NULL,
  `JOB_CLASS_NAME` VARCHAR(250) NOT NULL,
  `IS_DURABLE` VARCHAR(1) NOT NULL,
  `IS_NONCONCURRENT` VARCHAR(1) NOT NULL,
  `IS_UPDATE_DATA` VARCHAR(1) NOT NULL,
  `REQUESTS_RECOVERY` VARCHAR(1) NOT NULL,
  `JOB_DATA` BLOB,
  PRIMARY KEY (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`),
  KEY `IDX_QRTZ_J_REQ_RECOVERY` (`SCHED_NAME`,`REQUESTS_RECOVERY`),
  KEY `IDX_QRTZ_J_GRP` (`SCHED_NAME`,`JOB_GROUP`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `QRTZ_LOCKS` (
  `SCHED_NAME` VARCHAR(120) NOT NULL,
  `LOCK_NAME` VARCHAR(40) NOT NULL,
  PRIMARY KEY (`SCHED_NAME`,`LOCK_NAME`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `QRTZ_PAUSED_TRIGGER_GRPS` (
  `SCHED_NAME` VARCHAR(120) NOT NULL,
  `TRIGGER_GROUP` VARCHAR(190) NOT NULL,
  PRIMARY KEY (`SCHED_NAME`,`TRIGGER_GROUP`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `QRTZ_SCHEDULER_STATE` (
  `SCHED_NAME` VARCHAR(120) NOT NULL,
  `INSTANCE_NAME` VARCHAR(190) NOT NULL,
  `LAST_CHECKIN_TIME` BIGINT NOT NULL,
  `CHECKIN_INTERVAL` BIGINT NOT NULL,
  PRIMARY KEY (`SCHED_NAME`,`INSTANCE_NAME`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `QRTZ_TRIGGERS` (
  `SCHED_NAME` VARCHAR(120) NOT NULL,
  `TRIGGER_NAME` VARCHAR(190) NOT NULL,
  `TRIGGER_GROUP` VARCHAR(190) NOT NULL,
  `JOB_NAME` VARCHAR(190) NOT NULL,
  `JOB_GROUP` VARCHAR(190) NOT NULL,
  `DESCRIPTION` VARCHAR(250) DEFAULT NULL,
  `NEXT_FIRE_TIME` BIGINT DEFAULT NULL,
  `PREV_FIRE_TIME` BIGINT DEFAULT NULL,
  `PRIORITY` INT DEFAULT NULL,
  `TRIGGER_STATE` VARCHAR(16) NOT NULL,
  `TRIGGER_TYPE` VARCHAR(8) NOT NULL,
  `START_TIME` BIGINT NOT NULL,
  `END_TIME` BIGINT DEFAULT NULL,
  `CALENDAR_NAME` VARCHAR(190) DEFAULT NULL,
  `MISFIRE_INSTR` SMALLINT DEFAULT NULL,
  `JOB_DATA` BLOB,
  PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
  KEY `IDX_QRTZ_T_J` (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`),
  KEY `IDX_QRTZ_T_JG` (`SCHED_NAME`,`JOB_GROUP`),
  KEY `IDX_QRTZ_T_C` (`SCHED_NAME`,`CALENDAR_NAME`),
  KEY `IDX_QRTZ_T_G` (`SCHED_NAME`,`TRIGGER_GROUP`),
  KEY `IDX_QRTZ_T_STATE` (`SCHED_NAME`,`TRIGGER_STATE`),
  KEY `IDX_QRTZ_T_N_STATE` (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`,`TRIGGER_STATE`),
  KEY `IDX_QRTZ_T_N_G_STATE` (`SCHED_NAME`,`TRIGGER_GROUP`,`TRIGGER_STATE`),
  KEY `IDX_QRTZ_T_NEXT_FIRE_TIME` (`SCHED_NAME`,`NEXT_FIRE_TIME`),
  KEY `IDX_QRTZ_T_NFT_ST` (`SCHED_NAME`,`TRIGGER_STATE`,`NEXT_FIRE_TIME`),
  KEY `IDX_QRTZ_T_NFT_MISFIRE` (`SCHED_NAME`,`MISFIRE_INSTR`,`NEXT_FIRE_TIME`),
  KEY `IDX_QRTZ_T_NFT_ST_MISFIRE` (`SCHED_NAME`,`MISFIRE_INSTR`,`NEXT_FIRE_TIME`,`TRIGGER_STATE`),
  KEY `IDX_QRTZ_T_NFT_ST_MISFIRE_GRP` (`SCHED_NAME`,`MISFIRE_INSTR`,`NEXT_FIRE_TIME`,`TRIGGER_GROUP`,`TRIGGER_STATE`),
  CONSTRAINT `qrtz_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`) REFERENCES `QRTZ_JOB_DETAILS` (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `QRTZ_BLOB_TRIGGERS` (
  `SCHED_NAME` VARCHAR(120) NOT NULL,
  `TRIGGER_NAME` VARCHAR(190) NOT NULL,
  `TRIGGER_GROUP` VARCHAR(190) NOT NULL,
  `BLOB_DATA` BLOB,
  PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
  KEY `SCHED_NAME` (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
  CONSTRAINT `qrtz_blob_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `QRTZ_SIMPLE_TRIGGERS` (
  `SCHED_NAME` VARCHAR(120) NOT NULL,
  `TRIGGER_NAME` VARCHAR(190) NOT NULL,
  `TRIGGER_GROUP` VARCHAR(190) NOT NULL,
  `REPEAT_COUNT` BIGINT NOT NULL,
  `REPEAT_INTERVAL` BIGINT NOT NULL,
  `TIMES_TRIGGERED` BIGINT NOT NULL,
  PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
  CONSTRAINT `qrtz_simple_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `QRTZ_CRON_TRIGGERS` (
  `SCHED_NAME` VARCHAR(120) NOT NULL,
  `TRIGGER_NAME` VARCHAR(190) NOT NULL,
  `TRIGGER_GROUP` VARCHAR(190) NOT NULL,
  `CRON_EXPRESSION` VARCHAR(120) NOT NULL,
  `TIME_ZONE_ID` VARCHAR(80) DEFAULT NULL,
  PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
  CONSTRAINT `qrtz_cron_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `QRTZ_SIMPROP_TRIGGERS` (
  `SCHED_NAME` VARCHAR(120) NOT NULL,
  `TRIGGER_NAME` VARCHAR(190) NOT NULL,
  `TRIGGER_GROUP` VARCHAR(190) NOT NULL,
  `STR_PROP_1` VARCHAR(512) DEFAULT NULL,
  `STR_PROP_2` VARCHAR(512) DEFAULT NULL,
  `STR_PROP_3` VARCHAR(512) DEFAULT NULL,
  `INT_PROP_1` INT DEFAULT NULL,
  `INT_PROP_2` INT DEFAULT NULL,
  `LONG_PROP_1` BIGINT DEFAULT NULL,
  `LONG_PROP_2` BIGINT DEFAULT NULL,
  `DEC_PROP_1` DECIMAL(13,4) DEFAULT NULL,
  `DEC_PROP_2` DECIMAL(13,4) DEFAULT NULL,
  `BOOL_PROP_1` VARCHAR(1) DEFAULT NULL,
  `BOOL_PROP_2` VARCHAR(1) DEFAULT NULL,
  PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
  CONSTRAINT `qrtz_simprop_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;
  1. 配置 quartz.properties 到项目 resources 下
#调度配置
#调度器实例名称
org.quartz.scheduler.instanceName=SsmScheduler
#调度器实例编号自动生成
org.quartz.scheduler.instanceId=AUTO
#是否在Quartz执行一个job前使用UserTransaction
org.quartz.scheduler.wrapJobExecutionInUserTransaction=false

#线程池配置
#线程池的实现类
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
#线程池中的线程数量
org.quartz.threadPool.threadCount=10
#线程优先级
org.quartz.threadPool.threadPriority=5
#配置是否启动自动加载数据库内的定时任务,默认true
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
#是否设置为守护线程,设置后任务将不会执行
#org.quartz.threadPool.makeThreadsDaemons=true

#持久化方式配置
#JobDataMaps是否都为String类型
org.quartz.jobStore.useProperties=true
#数据表的前缀,默认QRTZ_
org.quartz.jobStore.tablePrefix=QRTZ_
#最大能忍受的触发超时时间
org.quartz.jobStore.misfireThreshold=60000
#是否以集群方式运行
org.quartz.jobStore.isClustered=true
#调度实例失效的检查时间间隔,单位毫秒
org.quartz.jobStore.clusterCheckinInterval=2000
#数据持久化方式,默认存储在内存中,此处使用数据库方式
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
#数据库代理类,一般org.quartz.impl.jdbcjobstore.StdJDBCDelegate可以满足大部分数据库
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#数据库别名 随便取
org.quartz.jobStore.dataSource=qzDS

#数据库连接池,将其设置为druid
org.quartz.dataSource.qzDS.connectionProvider.class=com.example.canal.quartz.DruidConnection
#数据库引擎
org.quartz.dataSource.qzDS.driver=com.mysql.jdbc.Driver
#数据库连接
org.quartz.dataSource.qzDS.URL=jdbc:mysql://localhost:3306/yang?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&useSSL=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
#数据库用户
org.quartz.dataSource.qzDS.user=root
#数据库密码
org.quartz.dataSource.qzDS.password=139926
#允许最大连接
org.quartz.dataSource.qzDS.maxConnection=5

#数据库表信息
#QRTZ_CALENDARS	存储QuartzCalendar信息
#QRTZ_CRON_TRIGGERS	存储CronTrigger,包括Cron表达式和时区信息
#QRTZ_FIRED_TRIGGERS	存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息
#QRTZ_PAUSED_TRIGGER_GRPS	存储已暂停的Trigger组的信息
#QRTZ_SCHEDULER_STATE	存储少量的有关Scheduler的状态信息,和别的Scheduler实例
#QRTZ_LOCKS	存储程序的悲观锁的信息
#QRTZ_JOB_DETAILS	存储每一个已配置的Job的详细信息
#QRTZ_SIMPLE_TRIGGERS	存储简单的Trigger,包括重复次数、间隔、以及已触的次数
#QRTZ_BLOB_TRIGGERS	Trigger作为Blob类型存储
#QRTZ_TRIGGERS	存储已配置的Trigger的信息
#QRTZ_SIMPROP_TRIGGERS
  1. 创建 QuartzJobFactory
package com.example.canal.quartz.base;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;

@Component
public class QuartzJobFactory extends AdaptableJobFactory {

    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        // 调用父类的方法
        Object jobInstance = super.createJobInstance(bundle);
        // 进行注入
        capableBeanFactory.autowireBean(jobInstance);
        return jobInstance;
    }
}
  1. 创建 quartz 配置类 QuartzConfig
package com.example.canal.quartz.base;

import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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.SchedulerFactoryBean;

import java.io.IOException;
import java.util.Optional;

@Configuration
public class QuartzConfig {

    @Autowired
    private QuartzJobFactory jobFactory;

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
        // 获取配置属性
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();

        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));

        // 创建SchedulerFactoryBean
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setQuartzProperties(propertiesFactoryBean.getObject());
        factory.setJobFactory(jobFactory);// 支持在JOB实例中注入其他的业务对象
        factory.setApplicationContextSchedulerContextKey("applicationContextKey");
        factory.setWaitForJobsToCompleteOnShutdown(true);// 这样当spring关闭时,会等待所有已经启动的quartz job结束后spring才能完全shutdown。
        factory.setOverwriteExistingJobs(false);// 是否覆盖己存在的Job
        factory.setStartupDelay(10);// QuartzScheduler 延时启动,应用启动完后 QuartzScheduler 再启动
        return factory;
    }

    /**
     * 通过SchedulerFactoryBean获取Scheduler的实例
     * @return
     * @throws IOException
     */
    @Bean(name = "scheduler")
    public Scheduler scheduler() throws IOException {
        return Optional.ofNullable(schedulerFactoryBean().getScheduler()).orElse(null);
    }
}
  1. 创建数据库连接池类
package com.example.canal.quartz;

import com.alibaba.druid.pool.DruidDataSource;
import org.quartz.SchedulerException;
import org.quartz.utils.ConnectionProvider;

import java.sql.Connection;
import java.sql.SQLException;

public class DruidConnection implements ConnectionProvider {

    // JDBC驱动
    private String driver;
    // JDBC连接串
    private String URL;
    // 数据库用户名
    private String user;
    // 数据库用户密码
    private String password;
    // 数据库最大连接数
    private int maxConnection;
    // 数据库SQL查询每次连接返回执行到连接池,以确保它仍然是有效的。
    private String validationQuery;

    private boolean validateOnCheckout;

    private int idleConnectionValidationSeconds;

    // public String maxCachedStatementsPerConnection;

    private String discardIdleConnectionsSeconds;

    private static final int DEFAULT_DB_MAX_CONNECTIONS = 10;

    public static final int DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION = 120;

    // Druid连接池
    private DruidDataSource datasource;

    @Override
    public Connection getConnection() throws SQLException {
        return datasource.getConnection();
    }

    @Override
    public void shutdown() throws SQLException {
        datasource.close();
    }

    @Override
    public void initialize() throws SQLException {
        if (this.URL == null) {
            throw new SQLException("DBPool could not be created: DB URL cannot be null");
        }

        if (this.driver == null) {
            throw new SQLException("DBPool driver could not be created: DB driver class name cannot be null!");
        }

        if (this.maxConnection < 0) {
            throw new SQLException(
                "DBPool maxConnectins could not be created: Max connections must be greater than zero!");
        }

        datasource = new DruidDataSource();
        try {
            datasource.setDriverClassName(this.driver);
        } catch (Exception e) {
            try {
                throw new SchedulerException("Problem setting driver class name on datasource: " + e.getMessage(), e);
            } catch (SchedulerException e1) {
            }
        }

        datasource.setUrl(this.URL);
        datasource.setUsername(this.user);
        datasource.setPassword(this.password);
        datasource.setMaxActive(this.maxConnection);
        datasource.setMinIdle(1);
        datasource.setMaxWait(0);
        datasource.setMaxPoolPreparedStatementPerConnectionSize(DEFAULT_DB_MAX_CONNECTIONS);

        if (this.validationQuery != null) {
            datasource.setValidationQuery(this.validationQuery);
            if (!this.validateOnCheckout)
                datasource.setTestOnReturn(true);
            else
                datasource.setTestOnBorrow(true);
            datasource.setValidationQueryTimeout(this.idleConnectionValidationSeconds);
        }
    }

    public String getDriver() {
        return driver;
    }

    public void setDriver(String driver) {
        this.driver = driver;
    }

    public String getURL() {
        return URL;
    }

    public void setURL(String URL) {
        this.URL = URL;
    }

    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getMaxConnection() {
        return maxConnection;
    }

    public void setMaxConnection(int maxConnection) {
        this.maxConnection = maxConnection;
    }

    public String getValidationQuery() {
        return validationQuery;
    }

    public void setValidationQuery(String validationQuery) {
        this.validationQuery = validationQuery;
    }

    public boolean isValidateOnCheckout() {
        return validateOnCheckout;
    }

    public void setValidateOnCheckout(boolean validateOnCheckout) {
        this.validateOnCheckout = validateOnCheckout;
    }

    public int getIdleConnectionValidationSeconds() {
        return idleConnectionValidationSeconds;
    }

    public void setIdleConnectionValidationSeconds(int idleConnectionValidationSeconds) {
        this.idleConnectionValidationSeconds = idleConnectionValidationSeconds;
    }

    public DruidDataSource getDatasource() {
        return datasource;
    }

    public void setDatasource(DruidDataSource datasource) {
        this.datasource = datasource;
    }

    public String getDiscardIdleConnectionsSeconds() {
        return discardIdleConnectionsSeconds;
    }

    public void setDiscardIdleConnectionsSeconds(String discardIdleConnectionsSeconds) {
        this.discardIdleConnectionsSeconds = discardIdleConnectionsSeconds;
    }
}
  1. 创建job实体类
package com.example.canal.quartz;

import java.io.Serializable;
import java.util.Map;

public class QuartzJob implements Serializable {

    // 任务名称
    private String jobName;

    // 任务所有组
    private String groupName;

    // 任务执行类
    private String jobClass;

    // 任务调度时间表达式
    private String cronExpression;

    // 附加参数
    private Map<String, Object> param;

    public String getJobName() {
        return jobName;
    }

    public void setJobName(String jobName) {
        this.jobName = jobName;
    }

    public String getGroupName() {
        return groupName;
    }

    public void setGroupName(String groupName) {
        this.groupName = groupName;
    }

    public String getJobClass() {
        return jobClass;
    }

    public void setJobClass(String jobClass) {
        this.jobClass = jobClass;
    }

    public String getCronExpression() {
        return cronExpression;
    }

    public void setCronExpression(String cronExpression) {
        this.cronExpression = cronExpression;
    }

    public Map<String, Object> getParam() {
        return param;
    }

    public void setParam(Map<String, Object> param) {
        this.param = param;
    }
}
  1. 创建任务类 HelloJob
package com.example.canal.quartz.base;

import org.quartz.Job;
import org.quartz.JobExecutionContext;

import java.util.Date;

public class HelloJob implements Job {

    @Override
    public void execute(JobExecutionContext context) {

        System.out.println(new Date() + "hello job");
    }
}
  1. 创建业务 Controller 层
package com.example.canal.quartz.Controller;

import com.example.canal.quartz.QuartzJob;
import com.example.canal.quartz.biz.QuartzJobService;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;

@Service
@RequestMapping("/quartz")
public class QuartzJobController {

    @Resource
    private QuartzJobService quartzJobService;

    /**
     * @Description: 添加任务
     * @Author: yangjj_tc
     * @Date: 2021/10/18 11:32
     */
    @RequestMapping("/addJob")
    public Object addJob(@RequestBody QuartzJob quartzJob) {
        quartzJobService.addJob(quartzJob.getJobClass(), quartzJob.getJobName(), quartzJob.getGroupName(),
            quartzJob.getCronExpression(), quartzJob.getParam());
        return HttpStatus.OK;
    }

    /**
     * @Description: 暂停任务
     * @Author: yangjj_tc
     * @Date: 2021/10/18 11:33
     */
    @RequestMapping("/pauseJob")
    public Object pauseJob(@RequestBody QuartzJob quartzJob) {
        quartzJobService.pauseJob(quartzJob.getJobName(), quartzJob.getGroupName());
        return HttpStatus.OK;
    }

    /**
     * @Description: 恢复任务
     * @Author: yangjj_tc
     * @Date: 2021/10/18 11:33
     */
    @RequestMapping("/resumeJob")
    public Object resumeJob(@RequestBody QuartzJob quartzJob) {
        quartzJobService.resumeJob(quartzJob.getJobName(), quartzJob.getGroupName());
        return HttpStatus.OK;
    }

    /**
     * @Description: 立即运行一次定时任务
     * @Author: yangjj_tc
     * @Date: 2021/10/18 11:35
     */
    @RequestMapping("/runOnce")
    public Object runOnce(@RequestBody QuartzJob quartzJob) {
        quartzJobService.runOnce(quartzJob.getJobName(), quartzJob.getGroupName());
        return HttpStatus.OK;
    }

    /**
     * @Description: 更新任务
     * @Author: yangjj_tc
     * @Date: 2021/10/18 11:33
     */
    @RequestMapping("/updateJob")
    public Object updateJob(@RequestBody QuartzJob quartzJob) {
        quartzJobService.updateJob(quartzJob.getJobName(), quartzJob.getGroupName(), quartzJob.getCronExpression(),
            quartzJob.getParam());
        return HttpStatus.OK;
    }

    /**
     * @Description: 删除任务
     * @Author: yangjj_tc
     * @Date: 2021/10/18 11:33
     */
    @RequestMapping("/deleteJob")
    public Object deleteJob(@RequestBody QuartzJob quartzJob) {
        quartzJobService.deleteJob(quartzJob.getJobName(), quartzJob.getGroupName());
        return HttpStatus.OK;
    }

    /**
     * @Description: 启动所有任务
     * @Author: yangjj_tc
     * @Date: 2021/10/18 11:34
     */
    @RequestMapping("/startAllJobs")
    public Object startAllJobs() {
        quartzJobService.startAllJobs();
        return HttpStatus.OK;
    }

    /**
     * @Description: 暂停所有任务
     * @Author: yangjj_tc
     * @Date: 2021/10/18 11:34
     */
    @RequestMapping("/pauseAllJobs")
    public Object pauseAllJobs() {
        quartzJobService.pauseAllJobs();
        return HttpStatus.OK;
    }

    /**
     * @Description: 恢复所有任务
     * @Author: yangjj_tc
     * @Date: 2021/10/18 11:34
     */
    @RequestMapping("/resumeAllJobs")
    public Object resumeAllJobs() {
        quartzJobService.resumeAllJobs();
        return HttpStatus.OK;
    }

    /**
     * @Description: 关闭所有任务
     * @Author: yangjj_tc
     * @Date: 2021/10/18 11:34
     */
    @RequestMapping("/shutdownAllJobs")
    public Object shutdownAllJobs() {
        quartzJobService.shutdownAllJobs();
        return HttpStatus.OK;
    }
}
  1. 创建业务 Service 层
package com.example.canal.quartz.biz;

import java.util.Map;

public interface QuartzJobService {

    void addJob(String clazzName, String jobName, String groupName, String cronExp, Map<String, Object> param);

    void pauseJob(String jobName, String groupName);

    void resumeJob(String jobName, String groupName);

    void runOnce(String jobName, String groupName);

    void updateJob(String jobName, String groupName, String cronExp, Map<String, Object> param);

    void deleteJob(String jobName, String groupName);

    void startAllJobs();

    void pauseAllJobs();

    void resumeAllJobs();

    void shutdownAllJobs();
}

实现类

package com.example.canal.quartz.biz;

import com.sun.org.slf4j.internal.Logger;
import com.sun.org.slf4j.internal.LoggerFactory;
import org.quartz.*;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Map;

@Service
public class QuartzJobServiceImpl implements QuartzJobService {

    @Resource
    private Scheduler scheduler;

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void addJob(String clazzName, String jobName, String groupName, String cronExp, Map<String, Object> param) {
        try {
            // 启动调度器,默认初始化的时候已经启动
            // scheduler.start();
            // 构建job信息
            Class<? extends Job> jobClass = (Class<? extends Job>)Class.forName(clazzName);
            JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, groupName).build();
            // 表达式调度构建器(即任务执行的时间)
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExp);
            // 按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger =
                TriggerBuilder.newTrigger().withIdentity(jobName, groupName).withSchedule(scheduleBuilder).build();
            // 获得JobDataMap,写入数据
            if (param != null) {
                trigger.getJobDataMap().putAll(param);
            }
            scheduler.scheduleJob(jobDetail, trigger);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    @Override
    public void pauseJob(String jobName, String groupName) {
        try {
            scheduler.pauseJob(JobKey.jobKey(jobName, groupName));
        } catch (SchedulerException e) {
            logger.error(e.getMessage());
        }
    }

    @Override
    public void resumeJob(String jobName, String groupName) {
        try {
            scheduler.resumeJob(JobKey.jobKey(jobName, groupName));
        } catch (SchedulerException e) {
            logger.error(e.getMessage());
        }
    }

    @Override
    public void runOnce(String jobName, String groupName) {
        try {
            scheduler.triggerJob(JobKey.jobKey(jobName, groupName));
        } catch (SchedulerException e) {
            logger.error(e.getMessage());
        }
    }

    @Override
    public void updateJob(String jobName, String groupName, String cronExp, Map<String, Object> param) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(jobName, groupName);
            CronTrigger trigger = (CronTrigger)scheduler.getTrigger(triggerKey);
            if (cronExp != null) {
                // 表达式调度构建器
                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExp);
                // 按新的cronExpression表达式重新构建trigger
                trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
            }
            // 修改map
            if (param != null) {
                trigger.getJobDataMap().putAll(param);
            }
            // 按新的trigger重新设置job执行
            scheduler.rescheduleJob(triggerKey, trigger);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    @Override
    public void deleteJob(String jobName, String groupName) {
        try {
            // 暂停、移除、删除
            scheduler.pauseTrigger(TriggerKey.triggerKey(jobName, groupName));
            scheduler.unscheduleJob(TriggerKey.triggerKey(jobName, groupName));
            scheduler.deleteJob(JobKey.jobKey(jobName, groupName));
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    @Override
    public void startAllJobs() {
        try {
            scheduler.start();
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    @Override
    public void pauseAllJobs() {
        try {
            scheduler.pauseAll();
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    @Override
    public void resumeAllJobs() {
        try {
            scheduler.resumeAll();
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    @Override
    public void shutdownAllJobs() {
        try {
            if (!scheduler.isShutdown()) {
                // 需谨慎操作关闭scheduler容器
                // scheduler生命周期结束,无法再 start() 启动scheduler
                scheduler.shutdown(true);
            }
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }
}

测试

调用 addJob 接口

{
	"jobName": "hello job",
	"groupName": "mysqlGroup",
	"jobClass": "com.example.canal.quartz.base.HelloJob",
	"cronExpression": "0 0/1 * * * ? ",
	"param": {
		"param": "world"
	}
}

控制台打印定时调度

在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值