当前位置: 首页 > news >正文

SpringBoot3实战:5分钟搞定Quartz动态定时任务管理(含数据库配置)

SpringBoot3实战:5分钟搞定Quartz动态定时任务管理(含数据库配置)

在当今快节奏的业务环境中,定时任务已成为企业级应用不可或缺的一部分。无论是每天凌晨的数据报表生成,还是每小时的系统状态检查,亦或是每分钟的订单状态扫描,定时任务都在默默地支撑着业务的正常运转。而Quartz作为Java领域最成熟的任务调度框架,与SpringBoot3的结合更是如虎添翼,让开发者能够轻松应对各种复杂的定时任务场景。

1. 环境准备与基础配置

1.1 项目依赖配置

首先创建一个新的SpringBoot3项目,在pom.xml中添加必要的依赖:

<dependencies> <!-- SpringBoot基础依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Quartz集成依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> <!-- 数据库相关依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> </dependencies>

1.2 数据库配置

application.yml中配置数据库连接和Quartz的持久化设置:

spring: datasource: url: jdbc:mysql://localhost:3306/quartz_demo?useSSL=false&serverTimezone=UTC username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver quartz: job-store-type: jdbc jdbc: initialize-schema: always properties: org: quartz: scheduler: instanceName: clusteredScheduler instanceId: AUTO jobStore: class: org.quartz.impl.jdbcjobstore.JobStoreTX driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate tablePrefix: QRTZ_ isClustered: true clusterCheckinInterval: 20000 useProperties: false threadPool: class: org.quartz.simpl.SimpleThreadPool threadCount: 10 threadPriority: 5

注意:在实际生产环境中,initialize-schema应该设置为never,并手动执行Quartz提供的SQL脚本来创建表结构。

2. 核心组件实现

2.1 任务实体定义

首先定义一个简单的任务实体,用于存储任务配置信息:

@Entity @Table(name = "sys_job") public class SysJob { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String jobName; private String jobGroup; private String jobClass; private String cronExpression; private String description; private Boolean concurrent; private String status; // Getters and Setters }

2.2 任务执行逻辑

创建一个基础任务类,所有具体任务都将继承这个类:

public abstract class BaseJob implements Job { private static final Logger logger = LoggerFactory.getLogger(BaseJob.class); @Override public void execute(JobExecutionContext context) throws JobExecutionException { long startTime = System.currentTimeMillis(); logger.info("任务开始执行 - {}", context.getJobDetail().getKey().getName()); try { doExecute(context); } catch (Exception e) { logger.error("任务执行异常", e); throw new JobExecutionException(e); } long endTime = System.currentTimeMillis(); logger.info("任务执行完成 - 耗时: {}ms", (endTime - startTime)); } protected abstract void doExecute(JobExecutionContext context) throws Exception; }

2.3 具体任务示例

实现一个具体的任务示例:

@Component public class SampleJob extends BaseJob { @Override protected void doExecute(JobExecutionContext context) throws Exception { // 这里编写具体的任务逻辑 System.out.println("SampleJob正在执行,当前时间: " + new Date()); } }

3. 动态任务管理

3.1 任务调度服务

创建一个任务调度服务,封装Quartz的核心操作:

@Service public class QuartzJobService { @Autowired private Scheduler scheduler; /** * 创建并调度任务 */ public void scheduleJob(SysJob sysJob) throws Exception { // 构建JobDetail JobDetail jobDetail = JobBuilder.newJob(getJobClass(sysJob.getJobClass())) .withIdentity(sysJob.getJobName(), sysJob.getJobGroup()) .withDescription(sysJob.getDescription()) .storeDurably() .build(); // 构建Trigger Trigger trigger = TriggerBuilder.newTrigger() .withIdentity(sysJob.getJobName() + "_Trigger", sysJob.getJobGroup()) .withSchedule(CronScheduleBuilder.cronSchedule(sysJob.getCronExpression())) .build(); // 调度任务 scheduler.scheduleJob(jobDetail, trigger); } /** * 暂停任务 */ public void pauseJob(String jobName, String jobGroup) throws SchedulerException { JobKey jobKey = new JobKey(jobName, jobGroup); scheduler.pauseJob(jobKey); } /** * 恢复任务 */ public void resumeJob(String jobName, String jobGroup) throws SchedulerException { JobKey jobKey = new JobKey(jobName, jobGroup); scheduler.resumeJob(jobKey); } /** * 删除任务 */ public void deleteJob(String jobName, String jobGroup) throws SchedulerException { JobKey jobKey = new JobKey(jobName, jobGroup); scheduler.deleteJob(jobKey); } /** * 立即执行一次任务 */ public void triggerJob(String jobName, String jobGroup) throws SchedulerException { JobKey jobKey = new JobKey(jobName, jobGroup); scheduler.triggerJob(jobKey); } /** * 更新任务调度时间 */ public void updateJobCron(SysJob sysJob) throws SchedulerException { TriggerKey triggerKey = TriggerKey.triggerKey(sysJob.getJobName() + "_Trigger", sysJob.getJobGroup()); // 获取触发器 CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); // 重新构建触发器 trigger = trigger.getTriggerBuilder() .withIdentity(triggerKey) .withSchedule(CronScheduleBuilder.cronSchedule(sysJob.getCronExpression())) .build(); // 重新调度任务 scheduler.rescheduleJob(triggerKey, trigger); } /** * 获取任务类 */ private Class<? extends Job> getJobClass(String className) throws ClassNotFoundException { return (Class<? extends Job>) Class.forName(className); } }

3.2 任务管理API

创建一个REST控制器,提供任务管理的API接口:

@RestController @RequestMapping("/api/jobs") public class JobController { @Autowired private QuartzJobService quartzJobService; @Autowired private SysJobRepository sysJobRepository; /** * 创建任务 */ @PostMapping public ResponseEntity<?> createJob(@RequestBody SysJob sysJob) throws Exception { sysJobRepository.save(sysJob); quartzJobService.scheduleJob(sysJob); return ResponseEntity.ok().build(); } /** * 暂停任务 */ @PostMapping("/{id}/pause") public ResponseEntity<?> pauseJob(@PathVariable Long id) throws SchedulerException { SysJob sysJob = sysJobRepository.findById(id).orElseThrow(); quartzJobService.pauseJob(sysJob.getJobName(), sysJob.getJobGroup()); return ResponseEntity.ok().build(); } /** * 恢复任务 */ @PostMapping("/{id}/resume") public ResponseEntity<?> resumeJob(@PathVariable Long id) throws SchedulerException { SysJob sysJob = sysJobRepository.findById(id).orElseThrow(); quartzJobService.resumeJob(sysJob.getJobName(), sysJob.getJobGroup()); return ResponseEntity.ok().build(); } /** * 删除任务 */ @DeleteMapping("/{id}") public ResponseEntity<?> deleteJob(@PathVariable Long id) throws SchedulerException { SysJob sysJob = sysJobRepository.findById(id).orElseThrow(); quartzJobService.deleteJob(sysJob.getJobName(), sysJob.getJobGroup()); sysJobRepository.delete(sysJob); return ResponseEntity.ok().build(); } /** * 更新任务调度时间 */ @PutMapping("/{id}/cron") public ResponseEntity<?> updateJobCron(@PathVariable Long id, @RequestBody String cronExpression) throws SchedulerException { SysJob sysJob = sysJobRepository.findById(id).orElseThrow(); sysJob.setCronExpression(cronExpression); sysJobRepository.save(sysJob); quartzJobService.updateJobCron(sysJob); return ResponseEntity.ok().build(); } /** * 立即执行一次任务 */ @PostMapping("/{id}/trigger") public ResponseEntity<?> triggerJob(@PathVariable Long id) throws SchedulerException { SysJob sysJob = sysJobRepository.findById(id).orElseThrow(); quartzJobService.triggerJob(sysJob.getJobName(), sysJob.getJobGroup()); return ResponseEntity.ok().build(); } }

4. 高级特性与最佳实践

4.1 集群环境下的任务调度

在集群环境下,Quartz的集群功能可以确保任务不会被多个节点重复执行。要实现这一点,需要:

  1. 确保所有节点使用相同的数据库配置
  2. 设置org.quartz.jobStore.isClustered=true
  3. 为每个节点配置唯一的instanceId
spring: quartz: properties: org: quartz: scheduler: instanceId: ${spring.application.name}-${random.uuid} jobStore: isClustered: true

4.2 任务执行日志记录

为了追踪任务执行情况,可以创建一个任务执行日志实体:

@Entity @Table(name = "sys_job_log") public class SysJobLog { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "job_id") private SysJob job; private Date startTime; private Date endTime; private Long executionTime; private String status; private String exception; // Getters and Setters }

然后在BaseJob中记录执行日志:

public abstract class BaseJob implements Job { @Autowired private SysJobLogRepository jobLogRepository; @Override public void execute(JobExecutionContext context) throws JobExecutionException { SysJobLog jobLog = new SysJobLog(); jobLog.setStartTime(new Date()); try { doExecute(context); jobLog.setStatus("SUCCESS"); } catch (Exception e) { jobLog.setStatus("FAILED"); jobLog.setException(ExceptionUtils.getStackTrace(e)); throw new JobExecutionException(e); } finally { jobLog.setEndTime(new Date()); jobLog.setExecutionTime(jobLog.getEndTime().getTime() - jobLog.getStartTime().getTime()); jobLogRepository.save(jobLog); } } protected abstract void doExecute(JobExecutionContext context) throws Exception; }

4.3 任务执行超时处理

为了防止任务长时间运行导致系统资源耗尽,可以实现任务超时控制:

public abstract class BaseJob implements Job { @Value("${quartz.job.timeout:300000}") // 默认5分钟超时 private long timeout; @Override public void execute(JobExecutionContext context) throws JobExecutionException { ExecutorService executor = Executors.newSingleThreadExecutor(); Future<?> future = executor.submit(() -> { try { doExecute(context); } catch (Exception e) { throw new RuntimeException(e); } }); try { future.get(timeout, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { future.cancel(true); throw new JobExecutionException("任务执行超时"); } catch (Exception e) { throw new JobExecutionException(e); } finally { executor.shutdownNow(); } } protected abstract void doExecute(JobExecutionContext context) throws Exception; }

5. 常见问题与解决方案

5.1 任务错过触发(Misfire)处理

当任务因为系统关闭或线程不足而错过触发时间时,Quartz提供了多种处理策略:

策略说明适用场景
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY忽略错过触发,立即执行需要尽快执行的任务
MISFIRE_INSTRUCTION_FIRE_NOW立即触发一次执行错过一次执行的任务
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT立即重新调度,保留剩余执行次数固定次数的任务
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT立即重新调度,保留剩余执行次数固定次数的任务
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT下次调度时间执行,保留剩余执行次数周期性任务
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT下次调度时间执行,保留剩余执行次数周期性任务

在代码中可以这样配置:

Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .withSchedule(CronScheduleBuilder.cronSchedule("0 0/5 * * * ?") .withMisfireHandlingInstructionFireAndProceed()) .build();

5.2 数据库连接池配置

Quartz在频繁操作数据库时可能会遇到连接池问题,建议配置专用的连接池:

spring: quartz: properties: org: quartz: jobStore: dataSource: quartzDataSource dataSource: quartzDataSource: provider: hikaricp driver: com.mysql.cj.jdbc.Driver URL: jdbc:mysql://localhost:3306/quartz_demo user: root password: yourpassword maxConnections: 10 validationQuery: select 1

5.3 任务并发控制

默认情况下,Quartz允许同一JobDetail的多个实例并发执行。如果需要禁止并发执行,可以在Job类上添加@DisallowConcurrentExecution注解:

@DisallowConcurrentExecution public class NonConcurrentJob extends BaseJob { // 实现略 }
http://www.jsqmd.com/news/534254/

相关文章:

  • yfinance:5分钟搞定金融数据获取,Python量化投资必备神器
  • 从零到一:用Arduino打造你的静音扫地机器人
  • Blender手绘贴图实战:从入门到精通
  • 从零开始理解VAE:变分自编码器的核心原理与实践指南
  • Attention机制可视化解读:用GRU解码器实现翻译任务中的动态权重分配
  • LangChain函数调用全解析:如何让ChatGPT自动查询天气和商品信息?
  • 亚洲美女-造相Z-Turbo镜像免配置:内置模型自动下载、校验、缓存与版本管理
  • SiameseAOE模型C盘清理日志分析:自动识别大文件类型与可清理建议
  • 基于STM32F407ZGT6与INMP441的I2S音频采集系统:从配置到数据流处理
  • 为什么Python适合Web开发?对比PHP/Node.js的5个优势
  • WuliArt Qwen-Image Turbo惊艳效果:低光照场景中暗部层次保留与高光不过曝控制
  • 医疗敏感数据脱敏迫在眉睫:用Python实现符合GDPR与《个人信息保护法》的差分隐私(附FDA认证级噪声注入模板)
  • Python实战:5步搞定脑电信号预处理(附OpenBCI数据清洗代码)
  • 从零到一:用Simulink+CubeMX玩转STM32 GPIO,图形化编程告别手写代码
  • AI写专著的秘密武器!实用软件推荐,开启专著创作新篇章
  • Gemma-3-270m效果实录:Ollama中生成技术博客大纲+段落扩写全过程
  • FPGA复位策略全流程验证:从RTL到实现后的仿真与电路解析
  • FlashPatch终极指南:三步解决Flash游戏无法播放的难题
  • SAP物料凭证跳号问题深度解析:从SNRO缓存调整到SM56缓存重置的实战指南
  • 2026年免登在线PDF转Word免费工具横评与选型指南
  • AMD ROCm深度学习实战:从零构建高性能AI推理架构
  • Qwen2.5-Omni:多模态流式交互的Thinker-Talker架构设计与TMRoPE同步优化
  • 3分钟掌握N_m3u8DL-CLI-SimpleG:让M3U8视频下载变得像复制粘贴一样简单
  • 避坑指南:Triton配置文件config.pbtxt里那些容易踩的坑(input/output参数详解)
  • Kimi内置19套结构化提示词全解析:从爆款文案到影评达人的实战技巧
  • 视觉SLAM必备:Pangolin 0.5版本在Ubuntu20.04上的完整配置流程
  • 如何用CoT蒸馏让Llama 3学会GPT-4的推理能力?保姆级教程
  • RNA-seq新手必看:如何正确选择FPKM、RPKM还是CPM指标?
  • 3大核心突破:M5Stack-Core-S3让AI语音助手开发效率提升10倍
  • 自动化工具GSE进阶指南:从流程混乱到高效自动化