SpringBoot3集成Quartz

news2024/11/24 15:56:15

标签:Quartz.Job.Scheduler;

一、简介

Quartz由Java编写的功能丰富的开源作业调度框架,可以集成到几乎任何Java应用程序中,并且能够创建多个作业调度;

在实际的业务中,有很多场景依赖定时任务,比如常见的:订单超时处理,数据报表统计分析,会员等周期性管理,业务识别和预警通知等;

二、工程搭建

1、工程结构

2、依赖管理

starter-quartz组件中,实际依赖的是quartz组件2.3.2版本,使用Quartz框架时,需要自定义任务和执行逻辑,以更加灵活的方式管理业务调度;

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
    <version>${spring-boot.version}</version>
</dependency>

3、数据库

Quartz框架使用的表结构在如图的路径下,本文选择MySQL数据库存储,除此之外自定义两张表:quartz_job任务表和quartz_log任务执行日志表;

4、配置文件

在配置文件中使用Druid组件连接boot-quartz数据库,对于Quartz框架,主要配置数据库存储,调度器的基础信息,以及执行任务的线程池;

spring:
  # 定时器配置
  quartz:
    # 使用数据库存储
    job-store-type: jdbc
    # 初始化完成后自动启动调度程序
    autoStartup: true
    properties:
      org:
        quartz:
          # 调度器配置
          scheduler:
            instanceName: bootQuartzScheduler
            instanceId: AUTO
          # 存储配置
          jobStore:
            class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            tablePrefix: qrtz_
            isClustered: true
            misfireThreshold: 12000
            clusterCheckinInterval: 15000
            useProperties: false
          # 线程池配置
          threadPool:
            threadNamePrefix: Boot_Job_Pool
            threadPriority: 5
            threadCount: 10
            class: org.quartz.simpl.SimpleThreadPool

三、Quartz用法

对于任务管理的相关Web接口,采用Swagger文档组件,接口和实体类添加注解后,访问IP:Port/swagger-ui/index.html地址即可;

1、初始化加载

在服务启动时执行init初始化方法,查询quartz_job表中运行和暂停状态的任务,判断触发器是否存在,如果不存在则创建,如果存在则更新;

@Service
public class QuartzJobService {

    @Resource
    private QuartzJobMapper quartzJobMapper ;
    @Resource
    private QuartzManage quartzManage;

    @PostConstruct
    public void init () {
        LambdaQueryWrapper<QuartzJob> queryWrapper = new LambdaQueryWrapper<>() ;
        queryWrapper.in(QuartzJob::getState,JobState.JOB_RUN.getStatus(),JobState.JOB_STOP.getStatus());
        List<QuartzJob> jobList = quartzJobMapper.selectList(queryWrapper);
        jobList.forEach(quartzJob -> {
            CronTrigger cronTrigger = quartzManage.getCronTrigger(quartzJob.getId()) ;
            if (Objects.isNull(cronTrigger)){
                quartzManage.createJob(quartzJob);
            } else {
                quartzManage.updateJob(quartzJob);
            }
        });
    }
}

2、新增任务

在创建任务时,需要定义JobKeyTriggerKey的构建规则,Key需要具备唯一性,通常使用任务表的主键ID,任务一般是基于Cron表达式被调度执行的;

@Component
public class QuartzManage {

    @Resource
    private Scheduler scheduler ;

    public void createJob (QuartzJob quartzJob){
        try {
            // 构建任务
            JobDetail jobDetail = JobBuilder.newJob(QuartzRecord.class).withIdentity(getJobKey(quartzJob.getId())).build() ;
            // 构建Cron调度器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
                                                .cronSchedule(quartzJob.getCronExpres())
                                                .withMisfireHandlingInstructionDoNothing() ;
            // 任务触发器
            CronTrigger trigger = TriggerBuilder.newTrigger()
                                                .withIdentity(getTriggerKey(quartzJob.getId()))
                                                .withSchedule(scheduleBuilder).build() ;
            jobDetail.getJobDataMap().put(QuartzJob.JOB_PARAM_KEY,quartzJob);
            scheduler.scheduleJob(jobDetail,trigger) ;
            // 状态校验
            checkStop(quartzJob) ;
        } catch (SchedulerException e){
            throw new RuntimeException("createJob Fail",e) ;
        }
    }
}

3、更新任务

先通过任务ID查询TriggerKey,对于更新来说,最常见的就是Cron表达式即调度规则的更新,或者任务的执行参数更新;

@Component
public class QuartzManage {

    @Resource
    private Scheduler scheduler ;

    public void updateJob(QuartzJob quartzJob) {
        try {
            // 查询触发器Key
            TriggerKey triggerKey = getTriggerKey(quartzJob.getId());
            // 构建Cron调度器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
                                                .cronSchedule(quartzJob.getCronExpres())
                                                .withMisfireHandlingInstructionDoNothing();
            // 任务触发器
            CronTrigger trigger = getCronTrigger(quartzJob.getId())
                                .getTriggerBuilder().withIdentity(triggerKey)
                                .withSchedule(scheduleBuilder).build();
            trigger.getJobDataMap().put(QuartzJob.JOB_PARAM_KEY, quartzJob);
            scheduler.rescheduleJob(triggerKey, trigger);
            // 状态校验
            checkStop(quartzJob) ;
        } catch (SchedulerException e) {
            throw new RuntimeException("updateJob Fail",e) ;
        }
    }
}

4、暂停任务

先通过任务ID查询JobKey,判断任务是非运行状态,则停止任务;

@Component
public class QuartzManage {

    @Resource
    private Scheduler scheduler ;

    public void checkStop (QuartzJob quartzJob){
        try {
            if(quartzJob.getState() != JobState.JOB_RUN.getStatus()){
                this.scheduler.pauseJob(getJobKey(quartzJob.getId()));
            }
        } catch (SchedulerException e){
            throw new RuntimeException("pauseJob Fail",e) ;
        }
    }

}

5、恢复任务

先通过任务ID查询JobKey,恢复任务正常执行;

@Component
public class QuartzManage {

    @Resource
    private Scheduler scheduler ;

    public void resumeJob (Integer jobId){
        try {
            this.scheduler.resumeJob(getJobKey(jobId));
        } catch (SchedulerException e){
            throw new RuntimeException("resumeJob Fail",e) ;
        }
    }
}

6、执行一次

传入任务主体,再通过任务ID查询JobKey,然后立即执行一次任务;

@Component
public class QuartzManage {

    @Resource
    private Scheduler scheduler ;
    
    public void run (QuartzJob quartzJob){
        try {
            JobDataMap dataMap = new JobDataMap() ;
            dataMap.put(QuartzJob.JOB_PARAM_KEY,quartzJob);
            this.scheduler.triggerJob(getJobKey(quartzJob.getId()),dataMap);
        } catch (SchedulerException e){
            throw new RuntimeException("run Fail",e) ;
        }
    }
}

7、删除任务

先通过任务ID查询JobKey,彻底删除任务;

@Component
public class QuartzManage {

    @Resource
    private Scheduler scheduler ;

    public void deleteJob (Integer jobId){
        try {
            scheduler.deleteJob(getJobKey(jobId));
        } catch (SchedulerException e){
            throw new RuntimeException("deleteJob Fail",e) ;
        }
    }
}

8、任务执行

Quartz被集成在Spring框架之后,任务类自然会以Bean对象的方式被管理,在任务创建时,设置要执行的作业类QuartzRecord,该类继承QuartzJobBean抽象类,通过重写executeInternal方法,来管理任务实际执行的逻辑;

public class QuartzRecord extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) {
        QuartzJob quartzJob = (QuartzJob)context.getMergedJobDataMap().get(QuartzJob.JOB_PARAM_KEY) ;
        QuartzLogService quartzLogService = (QuartzLogService)SpringContextUtil.getBean("quartzLogService") ;
        // 定时器日志记录
        QuartzLog quartzLog = new QuartzLog () ;
        quartzLog.setJobId(quartzJob.getId());
        quartzLog.setBeanName(quartzJob.getBeanName());
        quartzLog.setParams(quartzJob.getParams());
        quartzLog.setCreateTime(new Date());
        long beginTime = System.currentTimeMillis() ;
        try {
            // 加载并执行
            Object target = SpringContextUtil.getBean(quartzJob.getBeanName());
            Method method = target.getClass().getDeclaredMethod("run", String.class);
            method.invoke(target, quartzJob.getParams());
            long executeTime = System.currentTimeMillis() - beginTime;
            quartzLog.setTimes((int)executeTime);
            quartzLog.setState(LogState.LOG_SUS.getStatus());
        } catch (Exception e){
            // 异常信息
            long executeTime = System.currentTimeMillis() - beginTime;
            quartzLog.setTimes((int)executeTime);
            quartzLog.setState(LogState.LOG_FAIL.getStatus());
            quartzLog.setError(e.getMessage());
        } finally {
            // 保存执行日志
            quartzLogService.insert(quartzLog) ;
        }
    }
}

四、参考源码

文档仓库:
https://gitee.com/cicadasmile/butte-java-note

源码仓库:
https://gitee.com/cicadasmile/butte-spring-parent

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

438. 找到字符串中所有字母异位词---字典匹配滑动窗口才是最优解

438. 找到字符串中所有字母异位词 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;一开始字典匹配 参考代码&#xff1a;错误傻逼代码AC代码 原题链接&#xff1a; 438. 找到字符串中所有字母异位词 https://leetcode.cn/problems/find-all-anagrams-in-a-st…

CUDA执行模型

一、CUDA执行模型概述 二、线程束执行 1. 线程束与线程块 线程束是SM中基本的执行单元。 当一个线程块的网格被启动后&#xff0c;网格中的线程块分布在SM中。 一旦线程块被调度到一个SM中&#xff0c;线程块中的线程会被进一步划分成线程束。 一个线程束由32个连续的线程…

week4刷题

题解: F(n)F(n−1)F(n−2) 由于斐波那契数存在递推关系&#xff0c;因此可以使用动态规划求解。动态规划的状态转移方程即为上述递推关系&#xff0c;边界条件为 F(0)F(0)F(0) 和 F(1)F(1)F(1)。 class Solution { public:int fib(int n) {int MOD 1000000007;if (n < 2)…

Nginx 安装与部署

文章和代码已经归档至【Github仓库&#xff1a;https://github.com/timerring/front-end-tutorial 】或者公众号【AIShareLab】回复 nginx 也可获取。 文章目录 虚拟机安装CentOS7.4Linux配置配置上网配置静态ip Nginx的安装版本区别备份克隆 安装编译安装报错解决 启动Nginx防…

SpringBoot+Mybatis-Plus实现增删改查超详细步骤

目录 一、介绍 二、前期准备工作 &#xff08;一&#xff09; 创建springboot项目和创建数据库 三、项目配置 &#xff08;一&#xff09;pom.xl导入相关依赖 1.导入依赖 &#xff08;二&#xff09;yml文件中配置连接数据库 2.配置yml文件 四、代码的编写 数据库展…

阿里云FRP内网穿透挂载多台服务器

1. FRP介绍 FRP (Fast Reverse Proxy) 是比较流行的一款。FRP 是一个免费开源的用于内网穿透的反向代理应用&#xff0c;它支持 TCP、UDP 协议&#xff0c; 也为 http 和 https 协议提供了额外的支持。你可以粗略理解它是一个中转站&#xff0c; 帮你实现 公网 ←→ FRP(服务器…

vue复习。从安装到使用

vue官网&#xff1a;cn.vuejs.org vue安装 cnpm install -g vue/cli 查看是否安装成功 vue --version 创建一个项目 vue create vue-demo(项目名称) 这个取消掉。空格可选中或者取消。 运行项目&#xff1a; cd 进入到项目下 npm run serve 运行成功后&#xff0c;访问这…

Leetcode 21. 合并两个有序链表

题目描述 题目链接&#xff1a;https://leetcode.cn/problems/merge-two-sorted-lists/description/ 思路 两个链表都是升序链表&#xff0c;新建一个链表&#xff0c;引入伪头节点作为辅助节点&#xff0c;将各节点添加到伪节点之后&#xff0c;再用一个cur节点指向新链表的…

【C语言练习】——找出单身狗、详解atoi函数

目录 一.找出单身狗版本1版本2 二.atoi函数介绍atoi函数atoi函数的模拟实现 一.找出单身狗 版本1 题目&#xff1a; 一个数组中只有一个数字是出现一次&#xff0c;其他所有数字都出现了两次 找出这一个只出现一次的数字 一个数组比如是1、2、3、4、5、1、2、3、4 只有5出现一…

【力扣每日一题】2023.8.13 合并两个有序数组

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目给我们两个升序数组&#xff0c;让我们合并它们&#xff0c;要求合并之后仍然是升序&#xff0c;并且这个合并操作是在数组1原地修改…

前沿分享-中距离射频取电

目前来看&#xff0c;微能源有四种技术路线&#xff0c;一是环境光采集、温差转换采集、无线射频采集和振动能量采集。 无线射频微能源是在通信设备通信过程中自然产生的&#xff0c;可以通过射频能量芯片实现无线射频取电&#xff0c;能瞬间大功率储电和安全驱动负载。 通过射…

基于微信小程序的应届大学生招聘平台的设计与实现

伴随着社会以及科学技术的发展&#xff0c;互联网已经渗透在人们的身边&#xff0c;网络慢慢的变成了人们的生活必不可少的一部分&#xff0c;紧接着众多智能手机飞速的发展&#xff0c;小程序这一名词已不陌生&#xff0c;越来越多的企业、公司、高校、医院等机构都会使用小程…

Linux上的五种IO模型

文章目录 五种模型阻塞非阻塞IO复用信号驱动异步 五种模型 阻塞非阻塞复用IO异步 阻塞 &#x1f446;read同步IO 非阻塞 EAGAIN&#xff1a;没有事件到达&#xff0c;返回值为-1&#xff0c; errno为EAGAIN IO复用 信号驱动 异步 &#x1f446;&#xff1a;异步IO&#xff…

Leetcode数组篇 Day1

移除元素&#xff08;暴力版&#xff09; 1.注意越界问题&#xff1a;ji1 起点&#xff0c;j < size 移除元素&#xff08;双指针版&#xff09; 1.快慢指针&#xff0c;新数组就是不含有目标元素的数组 快&#xff1a;获取新数组中的元素 慢&#xff1a;获取新数组需更新位…

【佳佳怪文献分享】通过跨模态监督学习视觉运动

标题&#xff1a;Learning Visual Locomotion with Cross-Modal Supervision 作者&#xff1a;Antonio Loquercio, Ashish Kumar, Jitendra Malik 来源&#xff1a;2023 IEEE International Conference on Robotics and Automation (ICRA 2023) 这是佳佳怪分享的第4篇文章 …

电视企业继续乱收费,消费者则再用脚投票,销量加速下滑

分析机构给出的7月份数据显示中国的电视销量同比下滑了14.1%&#xff0c;环比则下滑了23.4%&#xff0c;消费者继续用脚投票&#xff0c;而电视企业也在压榨最后一滴利润&#xff0c;继续乱收费&#xff0c;引发消费者的不满。 近几年来国内电视市场价格战异常激烈&#xff0c;…

手把手教你使用USB的CDC+MSC复合设备(基于stm32f407)

学习 1 实验环境与说明2 USB CDC2.1 CDC代码生成2.2 通信设备&#xff08;CDC&#xff09;描述符2.2.1 设备描述符2.2.2 配置描述符 2.3 调试 3 USB MSC3.1 MSC代码生成3.2 大容量存储设备&#xff08;MSC&#xff09;描述符3.2.1 设备描述符2.2.2 配置描述符 3.3 调试 4 USB复…

Redis_亿级访问量数据处理

11. 亿级访问量数据处理 11.1 场景表述 手机APP用户登录信息&#xff0c;一天用户登录ID或设备ID电商或者美团平台&#xff0c;一个商品对应的评论文章对应的评论APP上有打卡信息网站上访问量统计统计新增用户第二天还留存商品评论的排序月活统计统计独立访客(Unique Vistito…

LeetCode150道面试经典题--单词规律(简单)

1.题目 给定一种规律 pattern 和一个字符串 s &#xff0c;判断 s 是否遵循相同的规律。 这里的 遵循 指完全匹配&#xff0c;例如&#xff0c; pattern 里的每个字母和字符串 s 中的每个非空单词之间存在着双向连接的对应规律。 2.示例 pattern"abba" s "c…

【java】基础——多态

多态基本知识思维导图 多态的代码实现&#xff0c;注意父类对象引用指向子类对象引用&#xff08;向上转型&#xff09;的方法&#xff0c;父类就可以调用子类重写的方法和派生的方法&#xff0c;但不能调用子类特有的方法&#xff1a; class Animal {public void makeSound()…