boot项目中定时任务quartz

最近换项目组,发现项目中定时任务使用的是quartz框架,上一篇文章[springboot定时任务]也是使用的quartz,只不过实现方式不同,于是整理下

定时任务常用方法有Quartz,Spring自带的Schedule框架

Quartz基础知识

quartz常用组件:调度器,触发器,作业。

常用类:jobdetail(定时任务。描述job的核心逻辑);trigger(触发器。一个触发器对应一个定时任务,而一个定时任务可以对应多个触发器);schedule(调度器。管理触发器及定时任务的协调工作,一个schedule可以有多个trigger和jobdetail)

quartz提供两种作业存储类型,RAMJobStore和JDBC作业。

RAMJobStore是内存存储。数据访问快,但由于是内存存储。若服务挂掉,运行的定时任务信息就会丢失。

JDBC是数据库存储。通过quartz.properties文件,持久化任务调度信息。若应用挂掉,重启后会继续执行定时任务。

集群模式

Quartz框架

若部署多台服务器,定时任务在某一时刻只能有一个节点执行,则使用持久化任务调度信息方案
quartz默认的集群方案,保证同一时刻相同触发器只能一个节点执行;若节点执行失败,则会分派到另一节点,中途也会自动检查失效的定时调度,若不成功,则其他节点会继续执行。

定时任务在集群环境下默认是多台服务器会同时执行,这时需要结合项目自己程序判断。

quartz定时任务的配置文件中有一属性 org.quartz.jobStore.isClustered=true则为集群方式,自动判断节点状态。

@Schedule注解定时任务

@Schedule注解会定时跑服务。但是若部署多台服务器时,则会导致每台服务器都会执行一次,解决办法1.指定某台服务器执行 2.使用redis锁 3.数据库状态标识记录。但这种方式若是服务器时间一样,并发时数据库标识在查询时未来得及更新,也会导致多台服务器执行定时任务。详情可见[定时任务的时间修改后立即生效​​​​​​​]

分布式多节点多活应用同一定时任务,多节点同时执行配置

pom.xml
<!--定时任务-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-quartz</artifactId>
	<version>2.7.18</version>
</dependency>
QuartzConfig类
package com.example.demo.config;

import com.example.demo.jobs.Test1Job;
import org.quartz.CronTrigger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import java.util.Objects;

/**
 * @Auther: lr
 * @Date: 2024/3/28 14:34
 * @Description:
 */
@Configuration
public class QuartzConfig {

    @Value("${cron.test1}")
    private String test1Cron;

    /**
     * 定时任务的信息载体
     * 通过FactoryBean来间接创建Job对象,若有多个Job对象,需定义多次方法
     * 需要重写Factory相关代码,实现spring容器管理jobdetail
     * @return
     */
    @Bean(name ="test1JobDetailBean" )
    public JobDetailFactoryBean initTest1JobDetailBean(){
        JobDetailFactoryBean jobDetailFactoryBean=new JobDetailFactoryBean();
        jobDetailFactoryBean.setJobClass(Test1Job.class);
        return jobDetailFactoryBean;
    }

    /**
     * 触发器
     * 就是Trigger的一个实现类型,其中用于定义周期时间的CronScheduleBuilder
     * 实际上,CronTrigger是用于管理一个cron表达式的类型
     * @return
     */
    @Bean(name = "test1CronTriggerBean")
    public CronTriggerFactoryBean initTest1CronTriggerBean(){
        CronTriggerFactoryBean cronTriggerFactoryBean=new CronTriggerFactoryBean();
        JobDetailFactoryBean jobDetailFactoryBean = this.initTest1JobDetailBean();
        cronTriggerFactoryBean.setJobDetail(Objects.requireNonNull(jobDetailFactoryBean.getObject()));
        cronTriggerFactoryBean.setCronExpression(test1Cron);
        return cronTriggerFactoryBean;
    }


    /**
     * 任务调度器
     * @param customJobFactory 定时任务的信息载体
     * @param cronTriggerFactoryBeans 触发器
     * @return
     */
    @Bean
    public SchedulerFactoryBean initSchedulerFactoryBean(CustomJobFactory customJobFactory,
                                                         CronTriggerFactoryBean[] cronTriggerFactoryBeans){
        SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
        factoryBean.setJobFactory(customJobFactory);
        CronTrigger[] cronTriggers=new CronTrigger[cronTriggerFactoryBeans.length];
        for(int i=0;i<cronTriggerFactoryBeans.length;i++){
            cronTriggers[i]=cronTriggerFactoryBeans[i].getObject();
        }
        //注册触发器,一个Scheduler可以注册若干个触发器
        factoryBean.setTriggers(cronTriggers);
        //为Scheduler设置Jobdetail的工厂,可以覆盖掉springboot默认提供的工厂,保证jobdetail自动装配有效
        factoryBean.setJobFactory(customJobFactory);
        return factoryBean;
    }

}
 Test1Job
package com.example.demo.jobs;

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

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @Auther: lr
 * @Date: 2024/3/28 14:42
 * @Description:
 */
@Slf4j
public class Test1Job implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        //todo
        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-mm-dd HH:mm");
        String format = simpleDateFormat.format(new Date());
        log.info("{}开始执行定时任务1: {}",format ,jobExecutionContext.getJobDetail().getKey().getName());
    }
}
运行结果

可以看到多台机器都会执行到该定时任务

​​​​​​​

分布式多节点多活应用同一定时任务,单节点执行配置

pom.xml
<!--定时任务-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-quartz</artifactId>
	<version>2.7.18</version>
</dependency>
<!--引入druid数据源-->
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid-spring-boot-starter</artifactId>
	<version>1.2.6</version>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-configuration-processor</artifactId>
	<optional>true</optional>
</dependency>
<!--定时任务-->
quartz.yml
org:
  quartz:
    dataSource:
      myDS:
        URL: jdbc:mysql://127.0.0.1:3306/test?serverTimezone=Hongkong&useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true
        driver: com.mysql.cj.jdbc.Driver
        maxConnections: 5
        user: root
        password: 123456
        connectionProvider:
          class: com.example.demo.config.QuartzConnectionProvider
    scheduler:
      instanceName: ClusterQuartz
      instanceId: AUTO
      rmi:
        export: false
        proxy: false
      wrapJobExecutionInUserTransaction: false
    jobStore:
      driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
      tablePrefix: QRTZ_
      isClustered: true
      acquireTriggersWithinLock: true
      misfireThreshold: 5000
      useProperties: true
      dataSource: myDS
    threadPool:
      class: org.quartz.simpl.SimpleThreadPool
      threadCount: 1
      threadPriority: 5
      threadsInheritContextClassLoaderOfInitializingThread: true
数据库表整理

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_LOCKS;
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_JOB_DETAILS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    JOB_NAME  VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    DESCRIPTION VARCHAR(250) 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 NULL,
    PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);

CREATE TABLE QRTZ_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    JOB_NAME  VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    DESCRIPTION VARCHAR(250) NULL,
    NEXT_FIRE_TIME BIGINT(13) NULL,
    PREV_FIRE_TIME BIGINT(13) NULL,
    PRIORITY INTEGER NULL,
    TRIGGER_STATE VARCHAR(16) NOT NULL,
    TRIGGER_TYPE VARCHAR(8) NOT NULL,
    START_TIME BIGINT(13) NOT NULL,
    END_TIME BIGINT(13) NULL,
    CALENDAR_NAME VARCHAR(200) NULL,
    MISFIRE_INSTR SMALLINT(2) NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
        REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
);

CREATE TABLE QRTZ_SIMPLE_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    REPEAT_COUNT BIGINT(7) NOT NULL,
    REPEAT_INTERVAL BIGINT(12) NOT NULL,
    TIMES_TRIGGERED BIGINT(10) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_CRON_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    CRON_EXPRESSION VARCHAR(200) NOT NULL,
    TIME_ZONE_ID VARCHAR(80),
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_SIMPROP_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) NULL,
    INT_PROP_1 INT NULL,
    INT_PROP_2 INT NULL,
    LONG_PROP_1 BIGINT NULL,
    LONG_PROP_2 BIGINT NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 VARCHAR(1) NULL,
    BOOL_PROP_2 VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_BLOB_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    BLOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

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

CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_GROUP  VARCHAR(200) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_FIRED_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    ENTRY_ID VARCHAR(95) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    INSTANCE_NAME VARCHAR(200) NOT NULL,
    FIRED_TIME BIGINT(13) NOT NULL,
    SCHED_TIME BIGINT(13) NOT NULL,
    PRIORITY INTEGER NOT NULL,
    STATE VARCHAR(16) NOT NULL,
    JOB_NAME VARCHAR(200) NULL,
    JOB_GROUP VARCHAR(200) NULL,
    IS_NONCONCURRENT VARCHAR(1) NULL,
    REQUESTS_RECOVERY VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);

CREATE TABLE QRTZ_SCHEDULER_STATE
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    INSTANCE_NAME VARCHAR(200) NOT NULL,
    LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
    CHECKIN_INTERVAL BIGINT(13) NOT NULL,
    PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);

CREATE TABLE QRTZ_LOCKS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    LOCK_NAME  VARCHAR(40) NOT NULL,
    PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);

commit;
QuartzSingleConfig配置文件
package com.example.demo.config;

import com.example.demo.jobs.Test2Job;
import org.quartz.CronTrigger;
import org.springframework.beans.factory.annotation.Qualifier;
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.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

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

/**
 * @Auther: lr
 * @Date: 2024/3/28 14:34
 * @Description:
 */
@Configuration
public class QuartzSingleConfig {

    @Value("${cron.test2}")
    private String test2Cron;

    /**
     * 加载quartz配置
     */
    @Bean
    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean=new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.yml"));
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }

    /**
     * 定时任务的信息载体
     *
     * @return
     */
    @Bean(name = "test2JobDetailBean")
    public JobDetailFactoryBean initTest2JobDetailBean() {
        JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
        jobDetailFactoryBean.setJobClass(Test2Job.class);
        jobDetailFactoryBean.setDurability(true);
        return jobDetailFactoryBean;
    }

    /**
     * 触发器
     *
     * @return
     */
    @Bean(name = "test2CronTriggerBean")
    public CronTriggerFactoryBean initTest2CronTriggerBean() {
        CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
        JobDetailFactoryBean jobDetailFactoryBean = this.initTest2JobDetailBean();
        cronTriggerFactoryBean.setJobDetail(Objects.requireNonNull(jobDetailFactoryBean.getObject()));
        cronTriggerFactoryBean.setCronExpression(test2Cron);
        return cronTriggerFactoryBean;
    }

    /**
     * 任务调度器
     *
     * @param customJobFactory        定时任务的信息载体
     * @param cronTriggerFactoryBeans 触发器
     * @return
     */
    @Bean(name = "schedulerFactoryBean")
    public SchedulerFactoryBean initSchedulerFactoryBean(CustomJobFactory customJobFactory,
                                                         CronTriggerFactoryBean[] cronTriggerFactoryBeans,
                                                         @Qualifier("dataSource") DataSource dataSource) throws IOException {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        //自动覆盖quartz.properties配置的数据源
        schedulerFactoryBean.setDataSource(dataSource);
        //设置quartz的配置文件
        schedulerFactoryBean.setQuartzProperties(quartzProperties());
        schedulerFactoryBean.setJobFactory(customJobFactory);
        CronTrigger[] cronTriggers = new CronTrigger[cronTriggerFactoryBeans.length];
        for (int i = 0; i < cronTriggerFactoryBeans.length; i++) {
            cronTriggers[i] = cronTriggerFactoryBeans[i].getObject();
        }
        schedulerFactoryBean.setTriggers(cronTriggers);
        // 用于quartz集群,QuartzScheduler 启动时更新己存在的Job
        schedulerFactoryBean.setOverwriteExistingJobs(true);
        //延长启动
        schedulerFactoryBean.setStartupDelay(1);
        return schedulerFactoryBean;
    }

}
Durid连接池quartz工具类 QuartzConnectionProvider
package com.example.demo.config;

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

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

/**
 * @Auther: lr
 * @Date: 2024/3/28 17:01
 * @Description:
 */
@Data
public class QuartzConnectionProvider implements ConnectionProvider {

    /*
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     *
     * 常量配置,与quartz.properties文件的key保持一致(去掉前缀),同时提供set方法,Quartz框架自动注入值。
     *
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */

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

    private boolean validateOnCheckout;

    private int idleConnectionValidationSeconds;

    public String maxCachedStatementsPerConnection;

    private String discardIdleConnectionsSeconds;

    public static final int DEFAULT_DB_MAX_CONNECTIONS = 10;

    public static final int DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION = 120;

    //Druid连接池
    private DruidDataSource datasource;

    /*
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     *
     * 接口实现
     *
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */
    public Connection getConnection() throws SQLException {
        return datasource.getConnection();
    }

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

    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(this.DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION);

        if (this.validationQuery != null) {
            datasource.setValidationQuery(this.validationQuery);
            if (!this.validateOnCheckout)
                datasource.setTestOnReturn(true);
            else
                datasource.setTestOnBorrow(true);
            datasource.setValidationQueryTimeout(this.idleConnectionValidationSeconds);
        }
    }
}
Test2Job类
package com.example.demo.jobs;

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

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @Auther: lr
 * @Date: 2024/3/29 9:29
 * @Description:
 */
@Slf4j
public class Test2Job implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        //todo
        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm");
        String format = simpleDateFormat.format(new Date());
        log.info("{}开始执行定时任务2: {}",format ,jobExecutionContext.getJobDetail().getKey().getName());
    }

}

启动项目后,可以看出数据库中定时任务的表达式已经存储在表中

 如图所示,定时任务的信息存储在表中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/652495.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深圳比创达EMC|EMI电磁干扰行业:行业发展的关键与挑战

在当今的高科技时代&#xff0c;电子产品无处不在&#xff0c;它们为我们的生活带来了极大的便利。然而&#xff0c;随着电子设备的普及和集成度的提高&#xff0c;电磁干扰&#xff08;EMI&#xff09;问题也日益凸显。 一、EMI电磁干扰行业&#xff1a;无处不在的挑战 电磁…

【全开源】宇鹿家政系统(FastAdmin+ThinkPHP+原生微信小程序)

&#xff1a;助力家政行业数字化升级 一、引言&#xff1a;家政服务的新篇章 随着移动互联网的普及和人们生活水平的提高&#xff0c;家政服务的需求日益增长。为了满足这一市场需求&#xff0c;并推动家政行业的数字化升级&#xff0c;我们特别推出了家政小程序系统源码。这…

不聚焦情绪,不精神内耗:成长的自我修炼

在我们的人生旅途中&#xff0c;总会遇到各种各样的困境和挑战。如何在逆境中保持积极的心态&#xff0c;专注于个人成长&#xff0c;是每一个人都需要面对和思考的问题。这篇文章将探讨如何不抱怨、不指责、不聚焦情绪、不精神内耗&#xff0c;专注于解决困境和个人成长。 问…

记一次 .NET某工控WPF程序被人恶搞的 卡死分析

一&#xff1a;背景 1. 讲故事 这一期程序故障除了做原理分析&#xff0c;还顺带吐槽一下&#xff0c;熟悉我的朋友都知道我分析dump是免费的&#xff0c;但免费不代表可以滥用我的宝贵时间&#xff0c;我不知道有些人故意恶搞卡死是想干嘛&#xff0c;不得而知&#xff0c;希…

光学测量反射率定标版

在光学测量和成像领域&#xff0c;准确性和一致性是至关重要的。为了确保设备能够提供可靠的数据&#xff0c;必须对其进行精确的校准。这就是反射率定标版发挥作用的地方。本文将深入探讨反射率定标版的概念、重要性、使用方式以及它们如何帮助科学家和工程师实现光学测量的精…

李飞飞亲自撰文:大模型不存在主观感觉能力,多少亿参数都不行

近日&#xff0c;李飞飞连同斯坦福大学以人为本人工智能研究所 HAI 联合主任 John Etchemendy 教授联合撰写了一篇文章&#xff0c;文章对 AI 到底有没有感觉能力&#xff08;sentient&#xff09;进行了深入探讨。 「空间智能是人工智能拼图中的关键一环。」知名「AI 教母」李…

JAVA 17

文章目录 概述一 语法层面变化1_JEP 409&#xff1a;密封类2_JEP 406&#xff1a;switch模式匹配&#xff08;预览&#xff09; 二 API层面变化1_JEP 414&#xff1a;Vector API&#xff08;第二个孵化器&#xff09;2_JEP 415&#xff1a;特定于上下文的反序列化过滤器 三 其他…

Mysql 8.0 主从复制及读写分离搭建记录

前言 搭建参考&#xff1a;搭建Mysql主从复制 为什么要做主从复制&#xff1f; 做数据的热备&#xff0c;作为后备数据库&#xff0c;主数据库服务器故障后&#xff0c;可切换到从数据库继续工作&#xff0c;避免数据丢失。架构的扩展。业务量越来越大&#xff0c;I/O访问频…

运营商系统快速上云的实践分享

运营商系统上云的背景 系统上云是数字经济发展的潮流&#xff0c;在数字化转型的浪潮中&#xff0c;上云已经成为推动各行各业创新和效率提升的关键力量。运营商作为服务行业和企业上云的服务商&#xff0c;积极响应国家号召的同时为行业上云打造案例标杆&#xff0c;自身的系统…

PS中常用的快捷速查表以及常用的工具速查表

PS中常用的快捷速查表&#xff1a;大家有需要可以收藏一下 文件菜单 新建 ... CtrlN 打开 ... CtrlO 在 Bridge 中浏览 ... AltCtrlO 打开为 ... AltShiftCtrlO 关闭 CtrlW 关闭全部 AltCtrlW 关闭并转到 Bridge... ShiftCtrlW 存储 CtrlS 存储为 ... Shi…

康医养产教服务平台发布会

五月的上海&#xff0c;繁花似锦。22号下午在上海市长宁区虹桥路1999号黎黎酒家隆重举办“康医养产教服务平台发布会&#xff01;” 【韩邑 康医养产教服务平台发起人】 【盛汇中国创造学会理事】 【宋舒易博士上海会会联盟发起人】 为促进健康产业链可持续发展&#xff0c;结…

计算机网络——在地址栏输入网址(URL)之后都发生了什么

网址&#xff0c;也叫域名&#xff0c;域名就像一个 IP 地址的可读版本&#xff0c;比如&#xff0c;百度的域名 www.baidu.com&#xff0c;他的 ip 是 110.242.68.3&#xff0c;输入 IP 一样可以跳转到百度搜索的页面&#xff0c;我想没有一个人没去记百度的 IP 吧。其实我们真…

钡铼技术BL205模块在智能制造产线的灵活配置与优化

钡铼技术的OPC UA耦合器BL205模块在智能制造产线中的灵活配置与优化是当今工业领域中的一个关键议题。随着工业4.0和数字化转型的不断推进&#xff0c;生产线的灵活性和智能化程度成为了企业追求的目标。在这一背景下&#xff0c;BL205模块以其分布式、可插拔、结构紧凑、可编程…

I.MX6ULL Linux 点灯实验理论及汇编点灯

系列文章目录 I.MX6ULL Linux C语言开发 I.MX6ULL Linux 点灯实验理论 系列文章目录一、I.MX6ULL GPIO二、I.MX6ULL IO 命名三、I.MX6ULL IO 复用四、I.MX6ULL IO 配置五、I.MX6ULL GPIO 配置六、I.MX6ULL GPIO 时钟使能七、硬件原理分析八、实验程序编写 一、I.MX6ULL GPIO 一…

嵌入式进阶——外部中断(EXTI)

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 STC8H中断外部中断外部中断编写配置外部中断调用中断触发函数 外部中断测试测试外部中断0测试外部中断2、3或者4 PCB中断设计 STC8…

Unity 生成模版代码

1、创建模版代码文本 using System.Collections; using System.Collections.Generic; using UnityEngine;public class ClassNameScritpItem : MonoBehaviour {public GameObject go;// Start is called before the first frame updatevoid Start(){go new GameObject();}// …

驱动开发学习之新旧字符设备接口,自动创建设备的点灯

1.前言 本章将介绍新旧字符设备接口,以及自动创建设备节点的点灯实验。 2.实验原理介绍 2.1.寄存器知识 学习过单片机的兄弟都知道&#xff0c;点灯有以下步骤&#xff1a; &#xff08;1&#xff09;开启相应的GPIO时钟 &#xff08;2&#xff09;如果需要配置复用&…

聊聊如何感知项目引入哪些功能特性

前言 使用过springcloud全家桶朋友&#xff0c;应该知道springcloud涉及的组件很多&#xff0c;为了让开发者快速了解项目引入了springcloud哪些组件&#xff0c;springcloud引入了HasFeatures&#xff0c;配合Actuator&#xff0c;可以让开发者感知到项目引入的组件功能类型、…

C++ std::reference_wrapper:让引用更强大

std::reference_wrapper 的通俗易懂解释 一、简介二、std::reference_wrapper 的初衷三、常用示例3.1、与 make_pair 和 make_tuple 一起使用3.2、引用容器3.3、通过 std::thread 按引用传递参数给启动函数3.4、引用作为类成员3.5、按引用传递函数对象3.6、与绑定表达式一起使用…

苹果手机备忘录共享到微信,为何显示不支持的类型

作为一名苹果手机用户&#xff0c;我深知其系统的流畅与便捷。然而&#xff0c;在日常使用中&#xff0c;我发现了一个令人不解的问题&#xff1a;为何苹果手机的原生备忘录无法直接分享到微信&#xff1f;每次当我尝试将备忘录里的内容共享给微信好友时&#xff0c;总会遇到“…