quartz介绍
本文是一个quartz的demo版本,下载源码后刷表并修改配置文件数据库连接即可运行。
quartz基本对象
- Scheduler:调度器,start()之后才可以正常调度任务。
- Jobdetail :维护job信息的对象,通过jobName,jobGroupName确定获得唯一任务对象。
- CronTrigger:cron表达式类型触发器,通过triggerName,triggerGroupName确定获得唯一任务对象,根据cron表达式触发job。
- JobDataMap相当于每个jobdetail的本地变量,可以存储key-value值。
demo源码链接
demo基本功能描述
- 定时任务增删改查
- 立即执行
- 暂停/恢复
demo页面效果图
- 列表页
- 任务新增/编辑
demo后端实现过程
1.新建springboot 工程并引入所需依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.44</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!--换成自己的数据库驱动-->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.0.0</version>
</dependency>
2.建表语句
3.配置quartz&mybatis
server:
port: xxxx
spring:
#quartz属性
quartz:
properties:
org:
quartz:
#quartz数据库连接
dataSource:
qzDS:
URL: *****
driver: org.postgresql.Driver
maxConnections: 10
password: *****
user: *****
#quartzjob存储方式
jobStore:
#jdbc持久化
class: org.quartz.impl.jdbcjobstore.JobStoreTX
#数据源连接名
dataSource: qzDS
#驱动代表
driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
#触发器忍耐时间 超过5s则错过
misfireThreshold: 5000
#表前缀
tablePrefix: QRTZ_
useProperties: false
scheduler:
instanceName: DEMO_JOBS_1.0
threadPool:
class: org.quartz.simpl.SimpleThreadPool
makeThreadsDaemons: true
threadCount: 5
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true
datasource:
url: *****
username: *****
password: *****
driver-class-name: org.postgresql.Driver
mybatis:
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: com.demo.task.entity
4.实现定时任务增删改查
- 新增mapper与对应的xml文件,对于quartz的表只推荐查询操作。
package com.demo.job.mapper;
import com.demo.job.entity.JobAndTrigger;
import java.util.List;
public interface JobAndTriggerMapper {
List<JobAndTrigger> getJobAndTriggerDetails();
void updateTriggerPreTriggerTime(Long time);
Integer queryJobByNameAndGroupName(String jobName,String jobGroupName);
}
<?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.demo.job.mapper.JobAndTriggerMapper">
<resultMap id="jobdetail" type="com.demo.job.entity.JobAndTrigger">
<result property="jobName" jdbcType="VARCHAR" column="JOB_NAME"/>
<result property="jobGroup" jdbcType="VARCHAR" column="JOB_GROUP"/>
<result property="jobClassName" jdbcType="VARCHAR" column="JOB_CLASS_NAME"/>
<result property="triggerName" jdbcType="VARCHAR" column="TRIGGER_NAME"/>
<result property="triggerGroup" jdbcType="VARCHAR" column="TRIGGER_GROUP"/>
<result property="prevFireTime" jdbcType="VARCHAR" column="PREV_FIRE_TIME"/>
<result property="nextFireTime" jdbcType="VARCHAR" column="NEXT_FIRE_TIME"/>
<result property="cronExpression" jdbcType="VARCHAR" column="CRON_EXPRESSION"/>
<result property="triggerState" jdbcType="VARCHAR" column="TRIGGER_STATE"/>
</resultMap>
<select id="queryJobByNameAndGroupName" resultType="java.lang.Integer">
SELECT COUNT(1) FROM qrtz_job_details WHERE JOB_NAME = #{jobName} AND JOB_GROUP = #{jobGroupName} AND SCHED_NAME='DEMO_JOBS_1.0'
</select>
<select id="getJobAndTriggerDetails" resultMap="jobdetail">
SELECT
a.JOB_NAME,
a.JOB_GROUP,
a.JOB_CLASS_NAME,
a.JOB_DATA,
b.TRIGGER_NAME,
b.TRIGGER_GROUP,
case
WHEN b.PREV_FIRE_TIME =-1
THEN '-'
else to_char(to_timestamp(b.PREV_FIRE_TIME/1000),'yyyy-MM-dd HH24:MI:SS') END as PREV_FIRE_TIME,
to_char(to_timestamp(b.NEXT_FIRE_TIME/1000),'yyyy-MM-dd HH24:MI:SS') as NEXT_FIRE_TIME,
b.TRIGGER_STATE,
c.CRON_EXPRESSION,
c.TIME_ZONE_ID
FROM
qrtz_job_details a
LEFT JOIN qrtz_triggers b ON a.JOB_NAME = b.JOB_NAME
AND b.TRIGGER_GROUP = a.JOB_GROUP
LEFT JOIN qrtz_cron_triggers c ON b.TRIGGER_NAME = c.TRIGGER_NAME
AND b.TRIGGER_GROUP = c.TRIGGER_GROUP
</select>
<update id="updateTriggerPreTriggerTime" parameterType="java.lang.Long">
update qrtz_triggers set PREV_FIRE_TIME = #{time}
</update>
</mapper>
entity
package com.demo.job.entity;
import lombok.Data;
/**
* @description:
* @author: jiangjie
* @date: 2019/12/24
*/
@Data
public class JobAndTrigger {
private String jobName;
private String jobGroup;
private String jobClassName;
private String triggerName;
private String triggerGroup;
private String cronExpression;
private String triggerState;
private String prevFireTime;
private String nextFireTime;
}
- 新增service与controller
package com.demo.job.service.impl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.demo.job.singleton.SystemCache;
import com.demo.job.constants.CommonConstant;
import com.demo.job.entity.JobAndTrigger;
import com.demo.job.entity.TaskInfo;
import com.demo.job.mapper.JobAndTriggerMapper;
import com.demo.job.service.IJobService;
import com.demo.job.vo.Result;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class JobServiceImpl implements IJobService {
private static Logger log = LoggerFactory.getLogger(JobServiceImpl.class);
@Autowired
private Scheduler scheduler;
@Resource
private JobAndTriggerMapper jobAndTriggerMapper;
@Override
public Result getJobAndTriggerList(int pageNum, int pageSize) {
Map<String, Object> map = new HashMap<>();
try {
PageHelper.startPage(pageNum, pageSize);
List<JobAndTrigger> list = jobAndTriggerMapper.getJobAndTriggerDetails();
PageInfo<JobAndTrigger> page = new PageInfo<JobAndTrigger>(list);
List<TaskInfo> taskInfoList = (List<TaskInfo>) SystemCache.getInstance().getCacheMap().get(CommonConstant.TASK_CACHE_KEY);
map.put("JobAndTriggerList", page.getList());
map.put("number", page.getTotal());
map.put("taskList", taskInfoList);
} catch (Exception e) {
e.printStackTrace();
Result.FAIL("查询失败");
}
return Result.OK(map);
}
@Override
public Result createJobAndTrigger(String jobClassName, String jobName, String jobGroupName,String cronExpression) {
//构建job信息
JobDetail jobDetail;
try {
jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass()).withIdentity(jobName, jobGroupName).build();
//表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
//按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder
.newTrigger()
.withIdentity(jobName, jobGroupName)
.withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail, trigger);
return Result.OK();
} catch (Exception e) {
e.printStackTrace();
log.error("创建定时任务失败 ", e);
return Result.FAIL("创建定时任务失败");
}
}
@Override
public Result pauseJob(String jobName, String jobGroupName) {
try {
scheduler.pauseJob(JobKey.jobKey(jobName, jobGroupName));
return Result.OK();
} catch (SchedulerException e) {
e.printStackTrace();
log.error("暂停定时任务失败", e);
return Result.FAIL("暂停定时任务失败");
}
}
@Override
public Result resumeJob(String jobClassName, String jobGroupName) {
try {
scheduler.resumeJob(JobKey.jobKey(jobClassName, jobGroupName));
return Result.OK();
} catch (SchedulerException e) {
e.printStackTrace();
log.error("恢复定时任务失败", e);
return Result.FAIL("恢复定时任务失败");
}
}
@Override
public Result rescheduleJob(String jobName, String jobGroupName, String cronExpression) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
CronTrigger cronTrigger = rebulidTriggerKey(jobName, jobGroupName, cronExpression);
scheduler.rescheduleJob(triggerKey, cronTrigger);
return Result.OK();
} catch (SchedulerException e) {
e.printStackTrace();
log.error("更新定时任务失败", e);
return Result.FAIL("更新定时任务失败");
}
}
@Override
public Result deleteJob(String jobClassName, String jobGroupName) {
try {
scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.deleteJob(JobKey.jobKey(jobClassName, jobGroupName));
return Result.OK();
} catch (SchedulerException e) {
e.printStackTrace();
log.error("删除定时任务失败", e);
return Result.FAIL("删除定时任务失败");
}
}
@Override
public Result triggerJobAtOnce(String jobName, String jobGroupName) {
JobKey jobKey = new JobKey(jobName, jobGroupName);
try {
scheduler.triggerJob(jobKey);
return Result.OK();
} catch (SchedulerException e) {
log.error("立即执行任务时发生异常", e);
return Result.FAIL("立即执行任务时发生异常");
}
}
@Override
public Result validateJobNameAndGroupName(String jobName, String jobGroupName) {
return Result.OK(jobAndTriggerMapper.queryJobByNameAndGroupName(jobName, jobGroupName) == 0);
}
private Job getClass(String classname) throws Exception {
Class<?> clazz = Class.forName(classname);
return (Job) clazz.newInstance();
}
private CronTrigger rebulidTriggerKey(String jobName, String jobGroupName, String cronExpression) {
//重置crontrigger
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
CronTrigger trigger = null;
try {
trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
} catch (SchedulerException e) {
e.printStackTrace();
}
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
return trigger;
}
}
package com.demo.job.controller;
import com.demo.job.service.IJobService;
import com.demo.job.vo.JobAddOrUpdateReqVo;
import com.demo.job.vo.JobOptReqVo;
import com.demo.job.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping(value = "/job")
public class JobController {
@Autowired
private IJobService jobService;
@ResponseBody
@PostMapping(value = "/addjob")
public Result addjob(@RequestBody JobAddOrUpdateReqVo jobAddOrUpdateReqVo) {
return jobService.createJobAndTrigger( jobAddOrUpdateReqVo.getJobClassName(),
jobAddOrUpdateReqVo.getJobName(),
jobAddOrUpdateReqVo.getJobGroupName(),
jobAddOrUpdateReqVo.getCronExpression());
}
@PostMapping(value = "/pausejob")
public Result pausejob(@RequestBody JobOptReqVo jobOptReqVo) {
return jobService.pauseJob(jobOptReqVo.getJobName(), jobOptReqVo.getJobGroupName());
}
@PostMapping(value = "/resumejob")
public Result resumejob(@RequestBody JobOptReqVo jobOptReqVo) {
return jobService.resumeJob(jobOptReqVo.getJobName(), jobOptReqVo.getJobGroupName());
}
@PostMapping(value = "/reschedulejob")
public Result rescheduleJob(@RequestBody JobAddOrUpdateReqVo jobAddOrUpdateReqVo) {
return jobService.rescheduleJob(
jobAddOrUpdateReqVo.getJobName(),
jobAddOrUpdateReqVo.getJobGroupName(),
jobAddOrUpdateReqVo.getCronExpression());
}
@PostMapping(value = "/deletejob")
public Result deletejob(@RequestBody JobOptReqVo jobOptReqVo) {
return jobService.deleteJob(jobOptReqVo.getJobName(), jobOptReqVo.getJobGroupName());
}
@GetMapping(value = "/queryjob")
public Result queryjob(@RequestParam(value = "pageNum") Integer pageNum, @RequestParam(value = "pageSize") Integer pageSize) {
return jobService.getJobAndTriggerList(pageNum, pageSize);
}
@PostMapping(value = "/triggerjob")
public Result triggerJobAtOnce(@RequestBody JobOptReqVo jobOptReqVo) {
return jobService.triggerJobAtOnce(jobOptReqVo.getJobName(), jobOptReqVo.getJobGroupName());
}
@PostMapping(value = "/validateJobNameAndGroupName")
public Result validateJobNameAndGroupName(@RequestBody JobOptReqVo jobOptReqVo) {
return jobService.validateJobNameAndGroupName(jobOptReqVo.getJobName(), jobOptReqVo.getJobGroupName());
}
}
- 自定义任务名称实现
- 新增注解定义task任务名称
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TaskNode {
String taskName();
}
- 新增系统缓存,用单例维护一个map缓存。
public class SystemCache {
private SystemCache() {
}
private static SystemCache sysCache = new SystemCache();
private Map<String, Object> mapCache = new HashMap();
public static SystemCache getInstance() {
return sysCache;
}
public Map<String, Object> getCacheMap() {
return mapCache;
}
}
- 新增ApplicationRunner监听器系统启动时扫描task包下所有任务并放入缓存,查询任务列表时从缓存取出返回
@Component
public class AppStaredListener implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
//获取系统缓存
Map<String, Object> cacheMap = SystemCache.getInstance().getCacheMap();
//获取task包下的所有任务
Set<Class<?>> taskSet = ClassUtil.getClasses("com.demo.job.task");
List<TaskInfo> taskInfoList = new ArrayList<>();
taskSet.stream().forEach(t -> {
TaskNode taskNode = t.getAnnotation(TaskNode.class);
if (taskNode != null) {
TaskInfo taskInfo = new TaskInfo();
taskInfo.setTaskClassName(t.getName());
taskInfo.setTaskName(taskNode.taskName());
taskInfoList.add(taskInfo);
}
});
//将定时任务信息放入缓存中
cacheMap.put(CommonConstant.TASK_CACHE_KEY, taskInfoList);
}
}
6.新增任务
- 新增定时任务,加上注解 execute 为具体任务逻辑
@TaskNode(taskName = "普通任务2")
public class Demo2Task implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("我是普通任务2");
}
}
demo启动
运行JobApplication 访问http://127.0.0.1:8088/JobPage.html 新建定时任务并立即执行
来源:CSDN
作者:username is null
链接:https://blog.csdn.net/qq_31061503/article/details/103822425