部分内容转载自“尔笑惹千愁”,链接https://blog.csdn.net/lx1309244704/article/details/81810373
在我们日常的开发中,很多时候,定时任务都不是写死的,而是写到数据库中,从而实现定时任务的动态配置,下面就通过一个简单的示例,来实现这个功能。
一、添加依赖包
```
<!-- quartz -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
```
二、创建调度器
```
package com.quartz;
import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
@Configuration
public class SchedulerConfig implements SchedulerFactoryBeanCustomizer{
@Override
public void customize(SchedulerFactoryBean schedulerFactoryBean) {
schedulerFactoryBean.setStartupDelay(2);
schedulerFactoryBean.setAutoStartup(true);
schedulerFactoryBean.setOverwriteExistingJobs(true);
}
}
```
三、application.yml配置
**这里需要注意的几点:**
**1、如果你的框架是已经搭建好,只需要在yml中添加quartz相关属性配置和数据库方式配置**
**2、quartz相关属性配置是在spring下管理,注意缩进格式,如果位置或者缩进不对程序启动后也不会报错**
```
server:
port: 8003
# 默认的profile为dev,其他环境通过指定启动参数使用不同的profile,比如:
# 测试环境:java -jar quartz-service.jar --spring.profiles.active=test
# 生产环境:java -jar quartz-service.jar --spring.profiles.active=prod
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource #这里是配置druid连接池,以下都是druid的配置信息
url: jdbc:mysql://0.0.0.0:3306/quartz?useUnicode=true&characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.jdbc.Driver
username: root
password: ******
quartz:
#相关属性配置
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: 10000
useProperties: false
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 10
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true
#数据库方式
job-store-type: jdbc
mybatis-plus:
mapper-locations: classpath*:/mapper/**Mapper.xml #把xml文件放在com.XX.mapper.*中可能会出现找到的问题,这里把他放在resource下的mapper中
typeAliasesPackage: com.quartz.domain #这里是实体类的位置,#实体扫描,多个package用逗号或者分号分隔
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
logging:
file: quartz-service.log
level:
com.quartz: debug
```
四、JobController
```
package com.south.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.south.data.vo.JobAndTriggerDto;
import com.south.service.IJobAndTriggerService;
import com.south.utils.PaginationUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.UriComponentsBuilder;
import java.util.List;
/**
* @Classname JobController
* @Description TODO
* @Date 2019/7/31 14:11
* @Created by zhangzhenjun
*/
@Slf4j
@RestController
@RequestMapping("/api")
public class JobResource {
@Autowired
private IJobAndTriggerService jobAndTriggerService;
public JobResource(IJobAndTriggerService jobAndTriggerService){
this.jobAndTriggerService = jobAndTriggerService;
}
@PostMapping(value = "/datagrid")
public ResponseEntity<List<JobAndTriggerDto>> queryjob(Pageable pageable, @RequestParam MultiValueMap<String, String> queryParams, UriComponentsBuilder uriBuilder) {
log.debug("queryjob");
IPage<JobAndTriggerDto> page = jobAndTriggerService.getPageJob(pageable, queryParams);
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(uriBuilder.queryParams(queryParams), page);
return ResponseEntity.ok().headers(headers).body(page.getRecords());
}
/**
* @Title: addJob
* @Description: TODO(添加Job)
* @param jobClassName
* 类名
* @param jobGroupName
* 组名
* @param cronExpression
* 表达式,如:0/5 * * * * ? (每隔5秒)
*/
@PostMapping(value = "/add")
public ResponseEntity addJob(
@RequestParam(value = "jobClassName") String jobClassName,
@RequestParam(value = "jobGroupName") String jobGroupName,
@RequestParam(value = "cronExpression") String cronExpression){
try {
jobAndTriggerService.addJob(jobClassName, jobGroupName, cronExpression);
return ResponseEntity.ok().body("操作成功");
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.ok().body("操作失败");
}
}
/**
* @Title: pauseJob
* @Description: TODO(暂停Job)
* @param jobClassName
* 类名
* @param jobGroupName
* 组名
*/
@PostMapping(value = "/pause")
public ResponseEntity pauseJob(
@RequestParam(value = "jobClassName") String jobClassName,
@RequestParam(value = "jobGroupName") String jobGroupName) {
try {
jobAndTriggerService.pauseJob(jobClassName, jobGroupName);
return ResponseEntity.ok().body("操作成功");
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.ok().body("操作失败");
}
}
/**
* @Title: resumeJob
* @Description: TODO(恢复Job)
* @param jobClassName
* 类名
* @param jobGroupName
* 组名
*/
@PostMapping(value = "/resume")
public ResponseEntity resumeJob(
@RequestParam(value = "jobClassName") String jobClassName,
@RequestParam(value = "jobGroupName") String jobGroupName) {
try {
jobAndTriggerService.resumejob(jobClassName, jobGroupName);
return ResponseEntity.ok().body("操作成功");
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.ok().body("操作失败");
}
}
/**
* @Title: rescheduleJob
* @Description: TODO(重新设置Job)
* @param jobClassName
* 类名
* @param jobGroupName
* 组名
* @param cronExpression
* 表达式
*/
@PostMapping(value = "/reschedule")
public ResponseEntity rescheduleJob(
@RequestParam(value = "jobClassName") String jobClassName,
@RequestParam(value = "jobGroupName") String jobGroupName,
@RequestParam(value = "cronExpression") String cronExpression) {
try {
jobAndTriggerService.updateJob(jobClassName, jobGroupName, cronExpression);
return ResponseEntity.ok().body("操作成功");
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.ok().body("操作失败");
}
}
/**
* @Title: deleteJob
* @Description: TODO(删除Job)
* @param jobClassName
* 类名
* @param jobGroupName
* 组名
*/
@RequestMapping(value = "/del", method = RequestMethod.POST)
public ResponseEntity deleteJob(@RequestParam(value = "jobClassName") String jobClassName, @RequestParam(value = "jobGroupName") String jobGroupName) {
try {
jobAndTriggerService.deleteJob(jobClassName, jobGroupName);
return ResponseEntity.ok().body("操作成功");
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.ok().body("操作失败");
}
}
}
```
五、IJobAndTriggerService
```
package com.south.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.south.data.vo.JobAndTriggerDto;
import org.springframework.data.domain.Pageable;
import org.springframework.util.MultiValueMap;
import java.util.Map;
/**
* @Classname IJobAndTriggerService
* @Description TODO
* @Date 2019/7/31 14:15
* @Created by zhangzhenjun
*/
public interface IJobAndTriggerService extends IService<JobAndTriggerDto> {
/**
* @Title: getPageJob
* @Description: TODO(查询定时任务,分页)
* @param @param search
* @param @return 参数
* @return Map<String,Object> 返回类型
* @throws
*/
IPage<JobAndTriggerDto> getPageJob(Pageable pageable, MultiValueMap queryParam);
/**
* @Title: getPageJobmod
* @Description: TODO(查询定时任务)
* @param @return 参数
* @return JobAndTriggerDto 返回类型
* @throws
*/
JobAndTriggerDto getPageJobmod();
/**
* @Title: addJob
* @Description: TODO(添加任务)
* @param @param jobClassName 任务路径名称
* @param @param jobGroupName 任务分组
* @param @param cronExpression cron时间规则
* @param @throws Exception 参数
* @return void 返回类型
* @throws
*/
void addJob(String jobClassName, String jobGroupName, String cronExpression) throws Exception;
/**
* @Title: addJob
* @Description: TODO(添加动态任务)
* @param @param jobClassName 任务路径名称
* @param @param jobGroupName 任务分组
* @param @param cronExpression cron时间规则
* @param @param jobDescription 参数
* @param @param params
* @param @throws Exception 参数说明
* @return void 返回类型
* @throws
*/
void addJob(String jobClassName, String jobGroupName, String cronExpression, String jobDescription, Map<String, Object> params) throws Exception;
/**
* @Title: updateJob
* @Description: TODO(更新定时任务)
* @param @param jobClassName 任务路径名称
* @param @param jobGroupName 任务分组
* @param @param cronExpression cron时间规则
* @param @throws Exception 参数
* @return void 返回类型
* @throws
*/
void updateJob(String jobClassName, String jobGroupName, String cronExpression) throws Exception;
/**
* @Title: deleteJob
* @Description: TODO(删除定时任务)
* @param @param jobClassName 任务路径名称
* @param @param jobGroupName 任务分组
* @param @throws Exception 参数
* @return void 返回类型
* @throws
*/
void deleteJob(String jobClassName, String jobGroupName) throws Exception;
/**
* @Title: pauseJob
* @Description: TODO(暂停定时任务)
* @param @param jobClassName 任务路径名称
* @param @param jobGroupName 任务分组
* @param @throws Exception 参数
* @return void 返回类型
* @throws
*/
void pauseJob(String jobClassName, String jobGroupName) throws Exception;
/**
* @Title: resumejob
* @Description: TODO(恢复任务)
* @param @param jobClassName 任务路径名称
* @param @param jobGroupName 任务分组
* @param @throws Exception 参数
* @return void 返回类型
* @throws
*/
void resumejob(String jobClassName, String jobGroupName) throws Exception;
}
```
六、IJobAndTriggerServiceImpl
```
package com.south.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.south.data.mapper.JobAndTriggerMapper;
import com.south.data.vo.JobAndTriggerDto;
import com.south.job.BaseJob;
import com.south.service.IJobAndTriggerService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.CronTrigger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.MultiValueMap;
import java.util.Iterator;
import java.util.Map;
/**
* @Classname IJobAndTriggerServiceImpl
* @Description TODO
* @Date 2019/7/31 14:30
* @Created by zhangzhenjun
*/
@Slf4j
@Service
@Transactional
public class IJobAndTriggerServiceImpl extends ServiceImpl<JobAndTriggerMapper, JobAndTriggerDto> implements IJobAndTriggerService {
@Autowired
private Scheduler scheduler;
@Override
public IPage<JobAndTriggerDto> getPageJob(Pageable pageable, MultiValueMap queryParam) {
IPage<JobAndTriggerDto> page = new Page<>(pageable.getPageNumber(), pageable.getPageSize());
return baseMapper.getJobAndTriggerDetails(page);
}
@Override
public JobAndTriggerDto getPageJobmod() {
return baseMapper.getJobAndTriggerDto();
}
@Override
public void addJob(String jobClassName, String jobGroupName, String cronExpression) throws Exception {
// 启动调度器
scheduler.start();
// 构建job信息
JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass())
.withIdentity(jobClassName, jobGroupName).build();
// 表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName)
.withSchedule(scheduleBuilder).build();
try {
scheduler.scheduleJob(jobDetail, trigger);
System.out.println("创建定时任务成功");
} catch (SchedulerException e) {
System.out.println("创建定时任务失败" + e);
throw new Exception("创建定时任务失败");
}
}
@Override
public void addJob(String jobClassName, String jobGroupName, String cronExpression, String jobDescription,
Map<String, Object> params) throws Exception {
// 启动调度器
scheduler.start();
// 构建job信息
JobDetail jobDetail = JobBuilder.newJob(IJobAndTriggerServiceImpl.getClass(jobClassName).getClass())
.withIdentity(jobClassName, jobGroupName).withDescription(jobDescription).build();
Iterator<Map.Entry<String, Object>> var7 = params.entrySet().iterator();
while(var7.hasNext()) {
Map.Entry<String, Object> entry = var7.next();
jobDetail.getJobDataMap().put((String)entry.getKey(), entry.getValue());
}
System.out.println("jobDetail数据:--------"+jobDetail.toString());
// 表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = (CronTrigger)TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName)
.withSchedule(scheduleBuilder).build();
try {
scheduler.scheduleJob(jobDetail, trigger);
System.out.println("创建定时任务成功");
} catch (SchedulerException e) {
System.out.println("创建定时任务失败" + e);
throw new Exception("创建定时任务失败");
}
}
@Override
public void updateJob(String jobClassName, String jobGroupName, String cronExpression) throws Exception {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(jobClassName, jobGroupName);
// 表达式调度构建器(动态修改后不立即执行)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
// 按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
System.out.println("更新定时任务失败" + e);
throw new Exception("更新定时任务失败");
}
}
@Override
public void deleteJob(String jobClassName, String jobGroupName) throws Exception {
scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.deleteJob(JobKey.jobKey(jobClassName, jobGroupName));
}
@Override
public void pauseJob(String jobClassName, String jobGroupName) throws Exception {
scheduler.pauseJob(JobKey.jobKey(jobClassName, jobGroupName));
}
@Override
public void resumejob(String jobClassName, String jobGroupName) throws Exception {
scheduler.resumeJob(JobKey.jobKey(jobClassName, jobGroupName));
}
public static BaseJob getClass(String classname) throws Exception {
Class<?> class1 = Class.forName(classname);
return (BaseJob) class1.newInstance();
}
}
```
七、JobAndTriggerMapper
```
package com.south.data.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.south.data.vo.JobAndTriggerDto;
/**
* @Classname JobAndTriggerMapper
* @Description TODO
* @Date 2019/7/31 14:31
* @Created by zhangzhenjun
*/
public interface JobAndTriggerMapper extends BaseMapper<JobAndTriggerDto> {
IPage<JobAndTriggerDto> getJobAndTriggerDetails(IPage<JobAndTriggerDto> page);
JobAndTriggerDto getJobAndTriggerDto();
}
```
八、JobAndTriggerMapper.xml
```
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.south.data.mapper.JobAndTriggerMapper">
<select id="getJobAndTriggerDetails" resultType="com.south.data.vo.JobAndTriggerDto">
SELECT
jd.JOB_NAME AS jobName,
jd.DESCRIPTION AS jobDescription,
jd.JOB_GROUP AS jobGroupName,
jd.JOB_CLASS_NAME AS jobClassName,
t.TRIGGER_NAME AS triggerName,
t.TRIGGER_GROUP AS triggerGroupName,
FROM_UNIXTIME(t.PREV_FIRE_TIME/1000,'%Y-%m-%d %T') AS prevFireTime,
FROM_UNIXTIME(t.NEXT_FIRE_TIME/1000,'%Y-%m-%d %T') AS nextFireTime,
ct.CRON_EXPRESSION AS cronExpression,
t.TRIGGER_STATE AS triggerState
FROM
qrtz_job_details jd
JOIN qrtz_triggers t
JOIN qrtz_cron_triggers ct ON jd.JOB_NAME = t.JOB_NAME
AND t.TRIGGER_NAME = ct.TRIGGER_NAME
AND t.TRIGGER_GROUP = ct.TRIGGER_GROUP
</select>
<select id="getJobAndTriggerDto" resultType="com.south.data.vo.JobAndTriggerDto">
SELECT
jd.JOB_NAME AS jobName,
jd.DESCRIPTION AS jobDescription,
jd.JOB_GROUP AS jobGroupName,
jd.JOB_CLASS_NAME AS jobClassName,
t.TRIGGER_NAME AS triggerName,
t.TRIGGER_GROUP AS triggerGroupName,
FROM_UNIXTIME(t.PREV_FIRE_TIME/1000,'%Y-%m-%d %T') AS prevFireTime,
FROM_UNIXTIME(t.NEXT_FIRE_TIME/1000,'%Y-%m-%d %T') AS nextFireTime,
ct.CRON_EXPRESSION AS cronExpression,
t.TRIGGER_STATE AS triggerState
FROM
qrtz_job_details jd
JOIN qrtz_triggers t
JOIN qrtz_cron_triggers ct ON jd.JOB_NAME = t.JOB_NAME
AND t.TRIGGER_NAME = ct.TRIGGER_NAME
AND t.TRIGGER_GROUP = ct.TRIGGER_GROUP
</select>
</mapper>
```
九、BaseJob
```
package com.south.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
* @Classname BaseJob
* @Description TODO
* @Date 2019/7/31 14:52
* @Created by zhangzhenjun
*/
public interface BaseJob extends Job {
public void execute(JobExecutionContext context) throws JobExecutionException;
}
```
十、HelloJob实例,在这里面写定时任务要执行的内容
```
package com.south.job;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
/**
* @Classname HelloJob
* @Description TODO
* @Date 2019/7/31 14:10
* @Created by zhangzhenjun
*/
@Slf4j
public class HelloJob implements BaseJob {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
log.error("Hello Job执行时间: " + new Date());
System.err.println("Hello Job执行时间: " + new Date());
}
}
```
十一、启动类加上@EnableScheduling注解,在项目启动时加载定时任务,启动Job
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190802091619840.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNTY3ODAx,size_16,color_FFFFFF,t_70)
项目效果:
![在这里插入图片描述](https://img-blog.csdnimg.cn/2019080209184473.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNTY3ODAx,size_16,color_FFFFFF,t_70)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190802091904900.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNTY3ODAx,size_16,color_FFFFFF,t_70)
如果需要源码,请跳转自本博文最上方的转载链接;
***最后再附上Java转Cron表达式的工具类,具体代码在我的另一篇博文:java生成cron表达式***
***链接:https://blog.csdn.net/qq_42567801/article/details/98172088***