# Quartz教程 - 9 动态调度

源码:获取源码 (opens new window)0分下载,给个好评


什么是动态调度?

动态调度就是不停止服务器,动态的控制定时任务的添加、修改、删除、启动、暂停等操作。

为什么需要动态调度?

如果现在有定时任务想停止运行或者修改执行时间,如果没有动态调度,那么就需要停止服务器,修改代码重新运行项目,具有很大的局限性。所以如果能在 web 页面动态控制定时任务的运行就很方便了。


例如有下面这样一个页面来管理定时任务就好了:

这里就不编写上面这个页面了,只实现后端的接口的实现。

下面来一步一步实现。


注意一下,如果你按照前面的内容进行了 Quartz 的持久化,那么 Quart 的表中可能有数据,但是下面操作重新整理了项目,删除了之前的一些任务类,这样启动项目的时候,Quartz会重新加载数据,找不到对应的任务类,会报错,所以需要先清空一下 Quartz 各个表的数据。

清空表的语句:

DELETE FROM QRTZ_LOCKS;
DELETE FROM QRTZ_CALENDARS;
DELETE FROM QRTZ_FIRED_TRIGGERS;
DELETE FROM QRTZ_PAUSED_TRIGGER_GRPS;
DELETE FROM QRTZ_SCHEDULER_STATE;
DELETE FROM QRTZ_BLOB_TRIGGERS;
DELETE FROM QRTZ_CRON_TRIGGERS;
DELETE FROM QRTZ_SIMPLE_TRIGGERS;
DELETE FROM QRTZ_SIMPROP_TRIGGERS;
DELETE FROM QRTZ_TRIGGERS;
DELETE FROM QRTZ_JOB_DETAILS;
1
2
3
4
5
6
7
8
9
10
11

# 1 新建SpringBoot项目

新建 SpringBoot 项目这里就不介绍了。可能你的项目已经是 SpringBoot 的项目了。


# 2 添加依赖

在项目的 pom.xml 中添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
1
2
3
4

直接使用 starter 的方式集成。


# 3 建表

因为我们需要对任务进行创建、删除、更新任务,所以要将任务保存下来,所以需要建表来保存我们的任务。

正常情况下,我们是有一个任务列表页面,显示所有的任务,可以对这些任务进行操作,也就是一个 CRUD 的功能。

CREATE TABLE tz_schedule_job (
  `id`								varchar(32) 	NOT NULL 			COMMENT 'ID',
  `job_name` 					varchar(128) 	NOT NULL 			COMMENT '任务名称',
  `bean_name` 				varchar(128)	NOT NULL 			COMMENT 'spring容器中bean的名称',
  `method_name` 			varchar(128)	NOT NULL 			COMMENT 'bean中的方法名',
  `cron_expression` 	varchar(32) 	NOT NULL 			COMMENT 'cron表达式',
  `params` 						varchar(512) 	DEFAULT NULL 	COMMENT '调用任务传递的参数',
  `status` 						tinyint(1) 		NOT NULL 			COMMENT '状态:1启用 0停用',
  `remark` 						varchar(512) 	DEFAULT NULL 	COMMENT '任务备注',
  `create_time` 			datetime		 	DEFAULT NULL 	COMMENT '创建时间',
  `update_time` 			datetime		 	DEFAULT NULL 	COMMENT '更新时间',
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
1
2
3
4
5
6
7
8
9
10
11
12
13

# 4 创建pojo和mapper

这里使用 mybatis-plus 来操作 tz_schedule_job 表数据,所以需要创建表对应的实体类。

ScheduleJob.java

package com.doubibiji.springbootquartz.pojo;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;

@Data
@TableName("tz_schedule_job")
public class ScheduleJob implements Serializable {

    // 任务id
    @TableId
    private String id;
    private String jobName;
    private String beanName;
    private String methodName;
    private String cronExpression;
    private String params;
    private Integer status;
    private String remark;
    private Date createTime;
    private Date updateTime;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

ScheduleJobMapper.java

package com.doubibiji.springbootquartz.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.doubibiji.springbootquartz.pojo.ScheduleJob;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface ScheduleJobMapper extends BaseMapper<ScheduleJob> {

    /**
     *  批量修改任务状态
     */
    int updateBatch(@Param("jobIds") String[] jobIds, @Param("status") int status);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

在 resources/mapper 下新建 ScheduleJobMapper.xml

ScheduleJobMapper.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.doubibiji.springbootquartz.mapper.ScheduleJobMapper">

	<!-- 批量更新状态 -->
	<update id="updateBatch">
		update tz_schedule_job set status = #{status} where id in
		<foreach item="jobId" collection="jobIds"  open="(" separator="," close=")">
			#{jobId}
		</foreach>
	</update>

</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13

# 5 业务类

业务类就是要处理定时任务实际要执行的业务逻辑的类。

后面定时任务会定时调用这个类中的方法,重复执行。

OrderServiceImpl.java

package com.doubibiji.springbootquartz.service.impl;

import com.doubibiji.springbootquartz.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service("orderService")
public class OrderServiceImpl implements OrderService {

    @Override
    public void checkOrderPayResult() {
        log.info("检查订单是否支付成功");
    }

    @Override
    public void checkOrderExpire() {
        log.info("检查订单是否超时");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

注意:一定要添加 @Service("orderService") 注解,后面会通过 orderService 名称到 Spring 容器中获取这个对象。


# 6 任务类

任务类只有一个,任务类接收实体类对象 ScheduleJob 。获取实体类中的 beanNamemethodName 属性,通过 beanName 到 Spring 容器中获取业务类对象,然后通过反射执行 methodName 指定的方法。

package com.doubibiji.springbootquartz.job;

import com.doubibiji.springbootquartz.pojo.ScheduleJob;
import com.doubibiji.springbootquartz.util.JobInvokeUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.springframework.scheduling.quartz.QuartzJobBean;

@Slf4j
public class QuartzJob extends QuartzJobBean {

    public static final String SCHEDULE_JOB = "SCHEDULE_JOB_KEY";

    @Override
    protected void executeInternal(JobExecutionContext context) {
        // 获取到ScheduleJob对象
        ScheduleJob scheduleJob = (ScheduleJob) context.getMergedJobDataMap().get(SCHEDULE_JOB);
        // 在这个方法中,通过ScheduleJob对象中的类创建对象,并触发指定的方法
        JobInvokeUtil.invokeMethod(scheduleJob);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

这里和前面实现定时任务有一些不同,在前面,我们有一个定时任务就创建一个任务类。这里我们只创建一个任务类,所有的任务都是通过这一个任务类来调用。也就是我们有多个任务的话,通过不同的触发器来调用这一个任务。然后在这个任务中,通过不同的参数,来确定调用哪个业务方法。


# 7 工具类

工具类主要处理通过 beanNamemethodName 的值找到 Spring 容器中的 业务类对象,然后通过反射执行 methodName 指定的方法。

JobInvokeUtil.java

package com.doubibiji.springbootquartz.util;

import cn.hutool.core.util.StrUtil;
import com.doubibiji.springbootquartz.pojo.ScheduleJob;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Method;

/**
 * 任务执行工具
 */
public class JobInvokeUtil {
    /**
     * 执行方法
     */
    public static void invokeMethod(ScheduleJob scheduleJob) {
        // 获取到bean
        Object target = SpringContextUtils.getBean(scheduleJob.getBeanName());
        try {
            if (StrUtil.isNotEmpty(scheduleJob.getParams())) {
                Method method = target.getClass().getDeclaredMethod(scheduleJob.getMethodName(), String.class);
                ReflectionUtils.makeAccessible(method);
                // 调用bean中的方法
                method.invoke(target, scheduleJob.getParams());
            } else {
                Method method = target.getClass().getDeclaredMethod(scheduleJob.getMethodName());
                ReflectionUtils.makeAccessible(method);
                // 调用bean中的方法
                method.invoke(target);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("执行定时任务失败", e);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

SpringContextUtils 是获取 Spring 容器中对象的工具类:

package com.doubibiji.springbootquartz.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * Spring Context 工具类
 */
@Component
public class SpringContextUtils implements ApplicationContextAware {
	public static ApplicationContext applicationContext; 

	@Override
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		SpringContextUtils.applicationContext = applicationContext;
	}

	public static Object getBean(String name) {
		return applicationContext.getBean(name);
	}

	public static <T> T getBean(String name, Class<T> requiredType) {
		return applicationContext.getBean(name, requiredType);
	}

	public static boolean containsBean(String name) {
		return applicationContext.containsBean(name);
	}

	public static boolean isSingleton(String name) {
		return applicationContext.isSingleton(name);
	}

	public static Class<? extends Object> getType(String name) {
		return applicationContext.getType(name);
	}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

还有一个工具类,就是操作 Quartz 任务的工具类。

ScheduleManager.java

package com.doubibiji.springbootquartz.config;

import com.doubibiji.springbootquartz.constant.ScheduleStatus;
import com.doubibiji.springbootquartz.job.QuartzJob;
import com.doubibiji.springbootquartz.pojo.ScheduleJob;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 定时任务工具类
 */
@Component
public class ScheduleManager {
    private final static String JOB_NAME = "TASK_";

    @Autowired
    private Scheduler scheduler;

    /**
     * 获取触发器key
     */
    private TriggerKey getTriggerKey(ScheduleJob scheduleJob) {
        return TriggerKey.triggerKey(JOB_NAME + scheduleJob.getId());
    }

    /**
     * 获取jobKey
     */
    private JobKey getJobKey(ScheduleJob scheduleJob) {
        return JobKey.jobKey(JOB_NAME + scheduleJob.getId());
    }

    /**
     * 获取表达式触发器
     */
    public CronTrigger getCronTrigger(ScheduleJob scheduleJob) {
        try {
            return (CronTrigger) scheduler.getTrigger(getTriggerKey(scheduleJob));
        } catch (SchedulerException e) {
            throw new RuntimeException("获取定时任务CronTrigger出现异常", e);
        }
    }

    /**
     * 创建定时任务
     */
    public void createScheduleJob(ScheduleJob scheduleJob) {
        try {
            //构建job信息
            JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class).withIdentity(getJobKey(scheduleJob)).build();

            //表达式调度构建器,可以根据scheduleJob修改withMisfireHandling方法
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression())
                    .withMisfireHandlingInstructionFireAndProceed();

            //按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(scheduleJob)).withSchedule(scheduleBuilder).build();

            // 放入参数,也就是要执行的任务信息,运行时的方法可以获取
            jobDetail.getJobDataMap().put(QuartzJob.SCHEDULE_JOB, scheduleJob);

            scheduler.scheduleJob(jobDetail, trigger);

            //暂停任务
            if (scheduleJob.getStatus().equals(ScheduleStatus.PAUSE.getType())) {
                pauseJob(scheduleJob);
            }
        } catch (SchedulerException e) {
            throw new RuntimeException("创建定时任务失败", e);
        }
    }

    /**
     * 更新定时任务
     */
    public void updateScheduleJob(ScheduleJob scheduleJob) {
        try {
            TriggerKey triggerKey = getTriggerKey(scheduleJob);

            //表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression()).withMisfireHandlingInstructionFireAndProceed();

            CronTrigger trigger = getCronTrigger(scheduleJob);

            // 如果定时任务不存在,则创建定时任务
            if (trigger == null) {
                createScheduleJob(scheduleJob);
                return;
            }

            //按新的cronExpression表达式重新构建trigger
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();

            // 传递参数
            trigger.getJobDataMap().put(QuartzJob.SCHEDULE_JOB, scheduleJob);

            scheduler.rescheduleJob(triggerKey, trigger);

            //暂停任务
            if (scheduleJob.getStatus().equals(ScheduleStatus.PAUSE.getType())) {
                pauseJob(scheduleJob);
            }

        } catch (SchedulerException e) {
            throw new RuntimeException("更新定时任务失败", e);
        }
    }

    /**
     * 立即执行任务
     */
    public void run(ScheduleJob scheduleJob) {
        try {
            //参数
            JobDataMap dataMap = new JobDataMap();
            dataMap.put(QuartzJob.SCHEDULE_JOB, scheduleJob);

            scheduler.triggerJob(getJobKey(scheduleJob), dataMap);
        } catch (SchedulerException e) {
            throw new RuntimeException("立即执行定时任务失败", e);
        }
    }

    /**
     * 暂停任务
     */
    public void pauseJob(ScheduleJob scheduleJob) {
        try {
            scheduler.pauseJob(getJobKey(scheduleJob));
        } catch (SchedulerException e) {
            throw new RuntimeException("暂停定时任务失败", e);
        }
    }

    /**
     * 恢复任务
     */
    public void resumeJob(ScheduleJob scheduleJob) {
        try {
            scheduler.resumeJob(getJobKey(scheduleJob));
        } catch (SchedulerException e) {
            throw new RuntimeException("恢复定时任务失败", e);
        }
    }

    /**
     * 删除定时任务
     */
    public void deleteScheduleJob(ScheduleJob scheduleJob) {
        try {
            // 停止触发器
            scheduler.pauseTrigger(getTriggerKey(scheduleJob));
            // 移除触发器
            scheduler.unscheduleJob(getTriggerKey(scheduleJob));
            // 删除任务
            scheduler.deleteJob(getJobKey(scheduleJob));
        } catch (SchedulerException e) {
            throw new RuntimeException("删除定时任务失败", e);
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163

解释一下 createScheduleJob(ScheduleJob scheduleJob) 方法。

该方法接收到 ScheduleJob 对象,通过该对象来创建定时任务,创建定时任务使用的就是前面的任务类,将 ScheduleJob 对象传递到任务类中,每次触发任务,在任务类中都通过传递的 ScheduleJob 对象,使用 JobInvokeUtil.invokeMethod(scheduleJob); 的方式调用业务类。

触发器使用 ScheduleJob 对象中的 cronExpression 属性来创建。

上面只是一个工具类,我们需要在维护任务的时候,调用上面的工具类。


# 8 任务维护

正常情况下,我们会有一个任务的维护页面,显示一个任务列表,然后在页面上通过按钮调用后端的接口。

这里只实现后端的接口,就不实现页面了。

接口实现:

ScheduleJobController.java

package com.doubibiji.springbootquartz.controller;

import com.doubibiji.springbootquartz.pojo.ScheduleJob;
import com.doubibiji.springbootquartz.service.ScheduleJobService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;

/**
 * 定时任务
 */
@Slf4j
@RestController
@RequestMapping("/schedule")
public class ScheduleJobController {

	@Autowired
	private ScheduleJobService scheduleJobService;
	
	/**
	 * 定时任务列表
	 */
	@GetMapping("/list")
	public ResponseEntity<List<ScheduleJob>> list(){
		List<ScheduleJob> scheduleJobList = scheduleJobService.list();
		return ResponseEntity.ok(scheduleJobList);
	}
	
	/**
	 * 保存定时任务
	 */
	@PostMapping
	public ResponseEntity<Void> save(@RequestBody ScheduleJob scheduleJob){
		scheduleJobService.saveAndStart(scheduleJob);
		return ResponseEntity.ok().build();
	}
	
	/**
	 * 修改定时任务
	 */
	@PutMapping
	public ResponseEntity<Void> update(@RequestBody ScheduleJob scheduleJob){
		scheduleJobService.updateScheduleJob(scheduleJob);
		return ResponseEntity.ok().build();
	}
	
	/**
	 * 删除定时任务
	 */
	@DeleteMapping
	public ResponseEntity<Void> delete(@RequestBody String[] jobIds){
		scheduleJobService.deleteBatch(jobIds);
		return ResponseEntity.ok().build();
	}
	
	/**
	 * 立即执行一次任务
	 */
	@PostMapping("/run")
	public ResponseEntity<Void> run(@RequestBody String[] jobIds){
		scheduleJobService.run(jobIds);
		return ResponseEntity.ok().build();
	}
	
	/**
	 * 暂停定时任务
	 */
	@PostMapping("/pause")
	public ResponseEntity<Void> pause(@RequestBody String[] jobIds){
		scheduleJobService.pause(jobIds);
		return ResponseEntity.ok().build();
	}
	
	/**
	 * 恢复定时任务
	 */
	@PostMapping("/resume")
	public ResponseEntity<Void> resume(@RequestBody String[] jobIds){
		scheduleJobService.resume(jobIds);
		return ResponseEntity.ok().build();
	}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

Service 实现

在 Service 中实现任务数据的维护,更新数据库的数据,同时调用 Quartz 工具类,修改任务的调度。

ScheduleJobServiceImpl.java

package com.doubibiji.springbootquartz.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.doubibiji.springbootquartz.config.ScheduleManager;
import com.doubibiji.springbootquartz.constant.ScheduleStatus;
import com.doubibiji.springbootquartz.mapper.ScheduleJobMapper;
import com.doubibiji.springbootquartz.pojo.ScheduleJob;
import com.doubibiji.springbootquartz.service.ScheduleJobService;
import org.quartz.CronTrigger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

@Service
public class ScheduleJobServiceImpl implements ScheduleJobService {
	
	@Autowired
	private ScheduleJobMapper scheduleJobMapper;
	@Autowired
	private ScheduleManager scheduleManager;

	/**
	 * 项目启动时,初始化定时器
	 */
	@PostConstruct
	public void init(){
		// 初始化的时候,查询到所有的定时任务,查看哪些定时任务需要启动
		List<ScheduleJob> scheduleJobList = scheduleJobMapper.selectList(null);

		for (ScheduleJob scheduleJob : scheduleJobList) {
			// 从调度器中获取触发器,查看是否需要创建、恢复、暂停
			CronTrigger trigger = scheduleManager.getCronTrigger(scheduleJob);
			// 如果定时任务不存在,则创建定时任务
			if (trigger == null) {
				scheduleManager.createScheduleJob(scheduleJob);
			}
			else if (ScheduleStatus.NORMAL.getType().equals(scheduleJob.getStatus())) {
				scheduleManager.resumeJob(scheduleJob);
			}
			else if (ScheduleStatus.PAUSE.getType().equals(scheduleJob.getStatus())) {
				scheduleManager.pauseJob(scheduleJob);
			}
		}
	}

	/**
	 * 查询所有的定时任务
	 */
	public List<ScheduleJob> list() {
		// 初始化的时候,查询到所有的定时任务,查看哪些定时任务需要启动
		List<ScheduleJob> scheduleJobList = scheduleJobMapper.selectList(null);
		return scheduleJobList;
	}

	/**
	 * 保存并开始定时任务
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void saveAndStart(ScheduleJob scheduleJob) {
		// 先查看是否已经有相同的定时任务了
		LambdaQueryWrapper<ScheduleJob> queryWrapper = new LambdaQueryWrapper<>();
		queryWrapper.eq(ScheduleJob::getBeanName, scheduleJob.getBeanName());
		queryWrapper.eq(ScheduleJob::getMethodName, scheduleJob.getMethodName());
		long sameCount = scheduleJobMapper.selectCount(queryWrapper);
		if (sameCount > 0) {
			throw new RuntimeException("与存在相同的定时任务");
		}

		scheduleJob.setCreateTime(new Date());
		scheduleJob.setStatus(ScheduleStatus.NORMAL.getType());
		scheduleJobMapper.insert(scheduleJob);
        // 创建定时任务
        scheduleManager.createScheduleJob(scheduleJob);
	}

	/**
	 * 更新任务
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void updateScheduleJob(ScheduleJob scheduleJob) {
		// 先查看是否已经有相同的定时任务了
		LambdaQueryWrapper<ScheduleJob> queryWrapper = new LambdaQueryWrapper<>();
		queryWrapper.eq(ScheduleJob::getBeanName, scheduleJob.getBeanName());
		queryWrapper.eq(ScheduleJob::getMethodName, scheduleJob.getMethodName());
		queryWrapper.ne(ScheduleJob::getId, scheduleJob.getId());
		long sameCount = scheduleJobMapper.selectCount(queryWrapper);
		if (sameCount > 0) {
			throw new RuntimeException("与存在相同的定时任务");
		}

		scheduleJob.setUpdateTime(new Date());
		scheduleManager.updateScheduleJob(scheduleJob);
		scheduleJobMapper.updateById(scheduleJob);
	}

	/**
	 * 删除任务
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void deleteBatch(String[] jobIds) {
		List<String> ids = Arrays.asList(jobIds);
		List<ScheduleJob> scheduleJobList = scheduleJobMapper.selectBatchIds(ids);
		for (ScheduleJob scheduleJob : scheduleJobList) {
			// 删除定时任务
			scheduleManager.deleteScheduleJob(scheduleJob);
		}
		scheduleJobMapper.deleteBatchIds(ids);
	}

	/**
	 * 立即执行一次任务
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
    public void run(String[] jobIds) {
    	for(String jobId : jobIds){
    		scheduleManager.run(scheduleJobMapper.selectById(jobId));
    	}
    }

	/**
	 * 暂停任务
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
    public void pause(String[] jobIds) {
		List<String> ids = Arrays.asList(jobIds);
		List<ScheduleJob> scheduleJobList = scheduleJobMapper.selectBatchIds(ids);
		for (ScheduleJob scheduleJob : scheduleJobList) {
			scheduleManager.pauseJob(scheduleJob);
		}

		// 更新任务状态
    	updateBatch(jobIds, ScheduleStatus.PAUSE.getType());
    }

	/**
	 * 恢复任务
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
    public void resume(String[] jobIds) {
		List<String> ids = Arrays.asList(jobIds);
		List<ScheduleJob> scheduleJobList = scheduleJobMapper.selectBatchIds(ids);
		for (ScheduleJob scheduleJob : scheduleJobList) {
			scheduleManager.resumeJob(scheduleJob);
		}

		// 更新任务状态
    	updateBatch(jobIds, ScheduleStatus.NORMAL.getType());
    }

	/**
	 *	批量更新任务状态
	 */
	public int updateBatch(String[] jobIds, int status) {
		return scheduleJobMapper.updateBatch(jobIds, status);
	}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167

# 9 项目配置

application.yaml

server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/doubibiji-db?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimum-idle: 3
      maximum-pool-size: 10
      idle-timeout: 10000
      connection-test-query: select 1
      initialization-fail-timeout: 15000
      connection-timeout: 15000

  # quartz 配置
  quartz:
    job-store-type: jdbc             # 使用jdbc的方式持久化
    jdbc:
      initialize-schema: never       # 启动后,不创建数据库表
    # 属性配置
    properties:
      org:
        quartz:
          # 集群配置
          scheduler:
            instanceName: MyScheduler       # 名称自定义,一起集群的各个服务器,名称要一致
            instanceId: AUTO                # 实例ID,各个集群的服务器,不能重复,这里使用自动生成就好了
          jobStore:
            isClustered: true               # 是否进行集群配置
            misfireThreshold: 10000					# 设置任务调度器的 Misfire 阈值为 10000 毫秒


# mybaits-plus配置
mybatis-plus:
  # MyBatis Mapper所对应的XML文件位置
  mapper-locations: classpath*:/mapper/*Mapper.xml
  global-config:
    # 关闭MP3.0自带的banner
    banner: false
    db-config:
      # 主键类型 0:数据库ID自增 1.未定义 2.用户输入 3 id_worker 4.uuid 5.id_worker字符串表示
      id-type: assign_uuid
      #字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
      field-strategy: NOT_NULL
      # 默认数据库表下划线命名
      table-underline: true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

项目结构如下:

# 10 调用

使用 Postman 调用接口进行测试。

创建任务:

后台执行结果:


暂停任务:

# 11 可能出现的错误

在执行任务的时候,可能出现以下错误,ScheduleJob 传递到任务类中,再次获取报转换错误的问题。

java.lang.ClassCastException: class com.doubibiji.springbootquartz.pojo.ScheduleJob cannot be cast to class com.doubibiji.springbootquartz.pojo.ScheduleJob (com.doubibiji.springbootquartz.pojo.ScheduleJob is in unnamed module of loader 'app'; com.doubibiji.springbootquartz.pojo.ScheduleJob is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @50d4c396)
	at com.doubibiji.springbootquartz.job.QuartzJob.executeInternal(QuartzJob.java:17) ~[classes/:na]
	at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:75) ~[spring-context-support-5.3.31.jar:5.3.31]
	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[quartz-2.3.2.jar:na]
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) ~[quartz-2.3.2.jar:na]

2024-01-05 21:12:03.223 ERROR 98290 --- [eduler_Worker-1] org.quartz.core.ErrorLogger              : Job (DEFAULT.TASK_92599230a8db58564fdda00b5813db12 threw an exception.

org.quartz.SchedulerException: Job threw an unhandled exception.
	at org.quartz.core.JobRunShell.run(JobRunShell.java:213) ~[quartz-2.3.2.jar:na]
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) ~[quartz-2.3.2.jar:na]
Caused by: java.lang.ClassCastException: class com.doubibiji.springbootquartz.pojo.ScheduleJob cannot be cast to class com.doubibiji.springbootquartz.pojo.ScheduleJob (com.doubibiji.springbootquartz.pojo.ScheduleJob is in unnamed module of loader 'app'; com.doubibiji.springbootquartz.pojo.ScheduleJob is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @50d4c396)
	at com.doubibiji.springbootquartz.job.QuartzJob.executeInternal(QuartzJob.java:17) ~[classes/:na]
	at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:75) ~[spring-context-support-5.3.31.jar:5.3.31]
	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[quartz-2.3.2.jar:na]
	... 1 common frames omitted
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

将项目中的 spring-boot-devtools 去掉就可以了。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>
1
2
3
4
5
6

源码:获取源码 (opens new window)0分下载,给个好评