SpringBoot整合Quartz
一、Quartz 基础概念
1. 什么是 Quartz
Quartz 是一款开源的 Java 任务调度框架,支持基于 Cron 表达式、固定间隔、固定时间点等多种调度规则,能实现任务的全生命周期管理(创建、暂停、恢复、删除等),且支持集群部署和任务持久化,是 SpringBoot 项目中定时任务的首选方案。
2. 核心作用
- 定时执行任务:如定时同步数据、发送通知、生成报表等
- 动态管理任务:无需重启应用即可调整任务状态
- 高可用支持:集群模式避免单点故障,持久化防止任务丢失
- 灵活调度策略:支持 Cron 表达式、简单间隔等多种触发方式
3. 核心组件
- Job:任务执行逻辑类(实现
org.quartz.Job接口) - JobDetail:任务元数据(名称、分组、参数等)
- Trigger:调度规则(CronTrigger 基于 Cron 表达式)
- Scheduler:调度器(绑定 Job 和 Trigger 并执行任务)
二、技术栈与环境准备
- 基础框架:Spring Boot 2.7.x
- 任务调度:Quartz 2.3.x(Spring Boot 默认集成)
- 数据库:MySQL
- ORM 框架:MyBatis-Plus 3.5.x
- 开发工具:IDEA、Maven
- JDK
三、项目搭建与配置

1. 创建 Maven 项目
新建 Spring Boot 项目,项目名为 SpringBootQuartz-Demo,包名设置为 com.yqd。
2. 添加依赖(pom.xml)
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.yqd</groupId>
<artifactId>SpringBootQuartz-Demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SpringBootQuartz-Demo</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- SpringBoot Web -->
<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>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>5.1.37</version>
</dependency>
<!-- Lombok(简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3. 配置文件(application.yml)
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/quartz_demo?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
quartz:
# 持久化到数据库
job-store-type: JDBC
jdbc:
# 初始化表结构(首次启动需设置为ALWAYS,后续改为NEVER)
initialize-schema: ALWAYS
properties:
org:
quartz:
scheduler:
instanceName: quartzScheduler
instanceId: AUTO # 自动生成实例ID
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
tablePrefix: QRTZ_ # 表前缀(Quartz默认)
isClustered: true # 集群模式(可选)
clusterCheckinInterval: 10000
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 10 # 线程池大小
threadPriority: 5
# MyBatis-Plus配置
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.yqd.entity
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志
四、数据库初始化
1. 创建数据库
执行 SQL 创建数据库:
CREATE DATABASE IF NOT EXISTS quartz_demo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
2. 自动生成 Quartz 内置表
启动项目后,Quartz 会自动在 quartz_demo 库中创建 11 张内置表(前缀 QRTZ_),无需手动干预。
3. 创建业务任务表(sys_job)
执行 SQL 创建自定义任务管理表:
CREATE TABLE `sys_job` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`job_name` varchar(100) NOT NULL COMMENT '任务名称',
`job_group` varchar(100) NOT NULL COMMENT '任务组',
`cron_expression` varchar(50) NOT NULL COMMENT 'Cron表达式',
`class_name` varchar(255) NOT NULL COMMENT '任务执行类全路径',
`status` int NOT NULL DEFAULT 1 COMMENT '状态(0-暂停,1-运行)',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_job_name_group` (`job_name`,`job_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='定时任务管理表';
五、核心代码实现(包名:com.yqd)
1. 实体类(JobEntity)
package com.yqd.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 定时任务实体类
*/
@Data
@TableName("sys_job")
public class JobEntity {
@TableId(type = IdType.AUTO)
private Long id;
private String jobName; // 任务名称
private String jobGroup; // 任务组
private String cronExpression; // Cron表达式
private String className; // 任务执行类全路径
private Integer status; // 状态:0-暂停,1-运行
private String remark; // 备注
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime; // 创建时间
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime; // 更新时间
}
2. MyBatis-Plus 自动填充配置
package com.yqd.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* 自动填充创建时间和更新时间
*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 插入时填充
this.strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class);
this.strictInsertFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
}
@Override
public void updateFill(MetaObject metaObject) {
// 更新时填充
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
}
}
3. 任务执行类(TestJob)
package com.yqd.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDateTime;
/**
* 示例任务:定时打印日志
*/
public class TestJob implements Job {
private static final Logger log = LoggerFactory.getLogger(TestJob.class);
/**
* 任务执行方法
*/
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 获取传递的参数
Long jobId = context.getJobDetail().getJobDataMap().getLong("jobId");
// 业务逻辑
log.info("===== TestJob 执行成功 =====");
log.info("任务ID:{}", jobId);
log.info("执行时间:{}", LocalDateTime.now());
log.info("==========================\n");
}
}
4. Quartz 工具类
package com.yqd.util;
import com.yqd.entity.JobEntity;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Quartz工具类:封装任务操作方法
*/
@Component
public class QuartzUtils {
@Autowired
private Scheduler scheduler; // 注入调度器
/**
* 创建定时任务
*/
public void createJob(JobEntity job) throws Exception {
// 1. 获取任务执行类
Class<? extends Job> jobClass = (Class<? extends Job>) Class.forName(job.getClassName());
// 2. 构建JobDetail
JobDetail jobDetail = JobBuilder.newJob(jobClass)
.withIdentity(job.getJobName(), job.getJobGroup()) // 唯一标识
.storeDurably() // 持久化
.build();
// 3. 设置任务参数
JobDataMap dataMap = jobDetail.getJobDataMap();
dataMap.put("jobId", job.getId());
// 4. 构建触发器
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(job.getJobName() + "_trigger", job.getJobGroup())
.withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression()))
.build();
// 5. 绑定任务和触发器
scheduler.scheduleJob(jobDetail, trigger);
// 6. 若状态为暂停,初始化为暂停状态
if (job.getStatus() == 0) {
pauseJob(job);
}
}
/**
* 暂停任务
*/
public void pauseJob(JobEntity job) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(job.getJobName(), job.getJobGroup());
scheduler.pauseJob(jobKey);
}
/**
* 恢复任务
*/
public void resumeJob(JobEntity job) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(job.getJobName(), job.getJobGroup());
scheduler.resumeJob(jobKey);
}
/**
* 删除任务
*/
public void deleteJob(JobEntity job) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(job.getJobName(), job.getJobGroup());
scheduler.deleteJob(jobKey);
}
/**
* 更新Cron表达式
*/
public void updateCron(JobEntity job) throws Exception {
TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName() + "_trigger", job.getJobGroup());
CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (oldTrigger == null) {
throw new RuntimeException("触发器不存在");
}
// 创建新触发器
CronTrigger newTrigger = oldTrigger.getTriggerBuilder()
.withIdentity(triggerKey)
.withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression()))
.build();
// 替换触发器
scheduler.rescheduleJob(triggerKey, newTrigger);
}
}
5. Mapper 接口
package com.yqd.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yqd.entity.JobEntity;
/**
* 任务Mapper接口
*/
public interface JobMapper extends BaseMapper<JobEntity> {
}
6. Service 层
6.1 Service 接口
package com.yqd.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yqd.entity.JobEntity;
/**
* 任务服务接口
*/
public interface JobService extends IService<JobEntity> {
void addJob(JobEntity job) throws Exception; // 添加任务
void updateJob(JobEntity job) throws Exception; // 更新任务
void deleteJob(Long id) throws Exception; // 删除任务
void pauseJob(Long id) throws Exception; // 暂停任务
void resumeJob(Long id) throws Exception; // 恢复任务
}
6.2 Service 实现类
package com.yqd.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yqd.entity.JobEntity;
import com.yqd.mapper.JobMapper;
import com.yqd.service.JobService;
import com.yqd.util.QuartzUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 任务服务实现类
*/
@Service
public class JobServiceImpl extends ServiceImpl<JobMapper, JobEntity> implements JobService {
@Autowired
private QuartzUtils quartzUtils;
/**
* 添加任务
*/
@Override
@Transactional
public void addJob(JobEntity job) throws Exception {
// 校验任务是否存在
QueryWrapper<JobEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("job_name", job.getJobName())
.eq("job_group", job.getJobGroup());
if (baseMapper.exists(queryWrapper)) {
throw new RuntimeException("任务已存在:" + job.getJobName() + "-" + job.getJobGroup());
}
// 保存到数据库
save(job);
// 创建Quartz任务
quartzUtils.createJob(job);
}
/**
* 更新任务
*/
@Override
@Transactional
public void updateJob(JobEntity job) throws Exception {
if (!existsById(job.getId())) {
throw new RuntimeException("任务不存在");
}
updateById(job);
quartzUtils.updateCron(job);
}
private boolean existsById(Long id) {
// 校验任务是否存在
QueryWrapper<JobEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("id", id);
return baseMapper.exists(queryWrapper);
}
/**
* 删除任务
*/
@Override
@Transactional
public void deleteJob(Long id) throws Exception {
JobEntity job = getById(id);
if (job == null) {
throw new RuntimeException("任务不存在");
}
quartzUtils.deleteJob(job);
removeById(id);
}
/**
* 暂停任务
*/
@Override
@Transactional
public void pauseJob(Long id) throws Exception {
JobEntity job = getById(id);
if (job == null) {
throw new RuntimeException("任务不存在");
}
quartzUtils.pauseJob(job);
job.setStatus(0);
updateById(job);
}
/**
* 恢复任务
*/
@Override
@Transactional
public void resumeJob(Long id) throws Exception {
JobEntity job = getById(id);
if (job == null) {
throw new RuntimeException("任务不存在");
}
quartzUtils.resumeJob(job);
job.setStatus(1);
updateById(job);
}
}
7. Controller 层
package com.yqd.controller;
import com.yqd.entity.JobEntity;
import com.yqd.service.JobService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 任务管理控制器
*/
@RestController
@RequestMapping("/job")
public class JobController {
@Autowired
private JobService jobService;
/**
* 添加任务
*/
@PostMapping("/add")
public String addJob(@RequestBody JobEntity job) {
try {
jobService.addJob(job);
return "添加成功:" + job.getJobName();
} catch (Exception e) {
return "添加失败:" + e.getMessage();
}
}
/**
* 更新任务
*/
@PostMapping("/update")
public String updateJob(@RequestBody JobEntity job) {
try {
jobService.updateJob(job);
return "更新成功:ID=" + job.getId();
} catch (Exception e) {
return "更新失败:" + e.getMessage();
}
}
/**
* 删除任务
*/
@DeleteMapping("/{id}")
public String deleteJob(@PathVariable Long id) {
try {
jobService.deleteJob(id);
return "删除成功:ID=" + id;
} catch (Exception e) {
return "删除失败:" + e.getMessage();
}
}
/**
* 暂停任务
*/
@GetMapping("/pause/{id}")
public String pauseJob(@PathVariable Long id) {
try {
jobService.pauseJob(id);
return "暂停成功:ID=" + id;
} catch (Exception e) {
return "暂停失败:" + e.getMessage();
}
}
/**
* 恢复任务
*/
@GetMapping("/resume/{id}")
public String resumeJob(@PathVariable Long id) {
try {
jobService.resumeJob(id);
return "恢复成功:ID=" + id;
} catch (Exception e) {
return "恢复失败:" + e.getMessage();
}
}
}
8.添加配置类QuartzConfig
package com.yqd.config;
import org.quartz.Scheduler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.sql.DataSource;
@Configuration
public class QuartzConfig {
// 注入Spring管理的数据源(确保数据源配置正确)
private final DataSource dataSource;
public QuartzConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 配置SchedulerFactoryBean,用于创建Scheduler
*/
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
// 设置数据源(Quartz持久化需要)
factory.setDataSource(dataSource);
// 其他可选配置:如任务工厂(如需依赖注入)
// factory.setJobFactory(new AutowiringSpringBeanJobFactory());
return factory;
}
/**
* 暴露Scheduler Bean,供其他类注入
*/
@Bean
public Scheduler scheduler() throws Exception {
return schedulerFactoryBean().getScheduler();
}
}
9. 启动类
package com.yqd;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 应用启动类
*/
@SpringBootApplication
@MapperScan("com.yqd.mapper") // 扫描Mapper接口
public class SpringBootQuartzDemo {
public static void main(String[] args) {
SpringApplication.run(SpringBootQuartzDemo.class, args);
}
}
六、测试流程
1. 启动项目
运行 SpringBootQuartzDemo 主类。
2. 添加任务(使用 Postman)
- 请求地址:
POST http://localhost:8080/job/add - 请求体(JSON):
{
"jobName": "testJob",
"jobGroup": "testGroup",
"cronExpression": "0/5 * * * * ?", // 每5秒执行一次
"className": "com.yqd.job.TestJob",
"status": 1,
"remark": "测试任务"
}
- 响应:
添加成功:testJob - 观察日志:控制台每 5 秒打印一次任务执行日志。
3. 测试其他接口
- 暂停任务:
GET http://localhost:8080/job/pause/{id} - 恢复任务:
GET http://localhost:8080/job/resume/{id} - 更新 Cron:
POST http://localhost:8080/job/update(修改 cronExpression) - 删除任务:
DELETE http://localhost:8080/job/{id}
七、生产环境注意事项
- 配置调整:
- 将
spring.quartz.jdbc.initialize-schema改为NEVER - 关闭 MyBatis-Plus SQL 日志(删除
log-impl配置)
- 将
- 集群部署:
- 开启
isClustered: true - 所有节点连接同一数据库
- 确保服务器时间同步
- 开启
- 任务类规范:
- 必须有无参构造方法
- 全路径在集群环境中保持一致
- 异常处理:
- 任务执行类中添加异常捕获
- 关键操作记录日志

浙公网安备 33010602011771号