实现定时任务数据库配置
# 使用数据库配置定时任务做任务调度
一般来说任务调度是开发写在代码里,或者用任务调度的框架。但是有的项目肯定是已经是开发中的,不能支持重新构造,那么就需要这种配置。在数据库编辑cron实现不终止项目的情况来更改任务调度的时间。
# 定时任务调度表
首先肯定需要一个定时任务表来记录并控制你的定时任务。
CREATE TABLE `spring_scheduled_cron` (
`cron_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`cron_key` varchar(128) NOT NULL COMMENT '定时任务完整类名',
`cron_expression` varchar(20) NOT NULL COMMENT 'cron表达式',
`task_explain` varchar(50) NOT NULL DEFAULT '' COMMENT '任务描述',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态,1:正常;0:停用',
PRIMARY KEY (`cron_id`),
UNIQUE KEY `cron_key` (`cron_key`),
UNIQUE KEY `cron_key_unique_idx` (`cron_key`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='定时任务表';
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
对应的表数据要记录在此,但是加了数据之后代码里面必须要有相应的定时任务类,不然项目无法启动。
INSERT INTO `spring_scheduled_cron` (`cron_id`, `cron_key`, `cron_expression`, `task_explain`, `status`)
VALUES (1, 'com.example.test.task.testTask', '0/5 * * * * ? ', '测试定时任务', 0);
1
2
2
# 定时任务实体类
/**
* @Description
* @Author wby
* @Date 2022-04-21
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName ( "spring_scheduled_cron" )
public class SpringScheduledCron implements Serializable {
private static final long serialVersionUID = 6867905359879360779L;
/**
* 主键id
*/
@TableId( "cron_id" )
private Long cronId;
/**
* 定时任务完整类名
*/
@TableField( "cron_key" )
private String cronKey;
/**
* cron表达式
*/
@TableField( "cron_expression" )
private String cronExpression;
/**
* 任务描述
*/
@TableField( "task_explain" )
private String taskExplain;
/**
* 状态,1:正常;0:停用
*/
@TableField( "status" )
private Integer 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
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
# 操作定时任务的mapper
@Component
@Mapper
public interface SpringScheduledCronMapper extends BaseMapper<SpringScheduledCron> {
@Select("select * from spring_scheduled_cron where cron_key = #{cronKey}")
SpringScheduledCron findByCronKey(String cronKey);
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 定时任务配置在spring
import com.example.yiwu.mysqlMapper.SpringScheduledCronMapper;
import com.example.yiwu.pojo.SpringScheduledCron;
import com.example.yiwu.task.ScheduledOfTask;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.util.Assert;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* @author wby
* @date 2022/4/22
*/
@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
@Autowired
private ApplicationContext context;
@Autowired
private SpringScheduledCronMapper springScheduledCronMapper;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
for (SpringScheduledCron springScheduledCron : springScheduledCronMapper.selectList(null)) {
Class<?> clazz;
Object task;
try {
clazz = Class.forName(springScheduledCron.getCronKey());
task = context.getBean(clazz);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("spring_scheduled_cron表数据" + springScheduledCron.getCronKey() + "有误", e);
} catch (BeansException e) {
throw new IllegalArgumentException(springScheduledCron.getCronKey() + "未纳入到spring管理", e);
}
Assert.isAssignable(ScheduledOfTask.class, task.getClass(), "定时任务类必须实现ScheduledOfTask接口");
// 可以通过改变数据库数据进而实现动态改变执行周期
taskRegistrar.addTriggerTask(((Runnable) task),
triggerContext -> {
String cronExpression = springScheduledCronMapper.selectById(springScheduledCron.getCronId()).getCronExpression();
return new CronTrigger(cronExpression).nextExecutionTime(triggerContext);
}
);
}
}
@Bean
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(10);
}
}
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
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
# 定时任务实现接口
import cn.hutool.extra.spring.SpringUtil;
import com.datanew.mapper.dfxx.SpringScheduledCronMapper;
import com.datanew.model.zcxm.SpringScheduledCron;
import java.util.Objects;
/**
* @Author 929
* @Date 2023/6/7 0007
*/
public interface ScheduledOfTask extends Runnable {
/**
* 定时任务方法
*/
void execute();
/**
* 实现控制定时任务启用或禁用的功能
*/
@Override
default void run() {
SpringScheduledCronMapper repository = SpringUtil.getBean(SpringScheduledCronMapper.class);
SpringScheduledCron scheduledCron = repository.findByCronKey(this.getClass().getName());
if (Objects.equals(scheduledCron.getStatus(),0)) {
// 任务是禁用状态
return;
}
execute();
}
}
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
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
# 未引入hutool时 使用的SpringUtil
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringUtils implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringUtils.context = applicationContext;
}
public static <T> T getBean(Class<T> clz) {
return context.getBean(clz);
}
public static Object getBean(String name) {
return context.getBean(name);
}
public ApplicationContext getApplicationContext() {
return context;
}
}
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
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
# 定时任务类
定时任务类必须实现ScheduledOfTask。不然会报错!!!!
@Component
@Slf4j
public class testTask implements ScheduledOfTask {
@Override
public void execute() {
log.info("=========================定时任务内容==============================");
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
这样你的定时任务就可以在数据库进行控制了!
# cron转换成时间
public class DateUtils {
/**
* 验证cron表达式是否正确
* @param cronExpression
* @return
*/
public static boolean isValid(String cronExpression){
return CronExpression.isValidExpression(cronExpression);
}
/**
* 将日期转换为cron时间
*
* @param date
* @return
*/
public static String parseCron(Date date) {
String format="ss mm HH dd MM ? yyyy";
SimpleDateFormat dateFormat = new SimpleDateFormat(format);
return dateFormat.format(date);
}
/**
* 获取下一次的执行时间
*
* @param cron
* @return
* @throws ParseException
*/
public static Date nextTime(String cron, Date date) throws ParseException {
// 加载包之后直接引用这个方法
CronExpression cronExpression = new CronExpression(cron);
// 转换 new Date 是为了给最近一次执行时间一个初始时间,这里给当前时间
Date nextTime = cronExpression.getNextValidTimeAfter(date);
return nextTime;
}
/**
* 获取10个下次执行时间,如果不足10次,则返回实际次数
* @param cron
* @param date
* @return
* @throws ParseException
*/
public static List<Date> next10Times(String cron, Date date) throws ParseException {
return nextTimes(cron,date,10);
}
/**
* 获取n个下次执行时间,如果不足n次,则返回实际次数
* @param cron
* @param date
* @param n
* @return
* @throws ParseException
*/
public static List<Date> nextTimes(String cron, Date date, int n) throws ParseException {
List<Date> nextTimes = new ArrayList<>();
Date nextTime = date;
for (int i = 0; i < n; i++) {
Date date1 = nextTime(cron, nextTime);
if (null == date1) {
break;
}
nextTimes.add(date1);
nextTime = date1;
}
return nextTimes;
}
/**
* 时间转字符串
* @param date
* @return
*/
public static String parseDate(Date date){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String dateFormat = sdf.format(date);
return dateFormat;
}
/**
* 字符串转时间
* @param dateString
* @return
* @throws ParseException
*/
public static Date toDate(String dateString) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date dateFormatParse = sdf.parse(dateString);
return dateFormatParse;
}
}
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
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
# 时间转换成cron
public class CronUtils {
/**
* 每年时间format格式
*/
public static final String DATEFORMAT_YEAR = "ss mm HH dd MM ? yyyy";
/**
* 每天时间format格式
*/
public static final String DATEFORMAT_EVERYDAY = "ss mm HH * * ?";
/**
* 每周时间format格式
*/
public static final String SUNDAY = "ss mm HH ? * 1";
public static final String MONDAY = "ss mm HH ? * 2";
public static final String TUESDAY = "ss mm HH ? * 3";
public static final String WEDNESDAY = "ss mm HH ? * 4";
public static final String THURSDAY = "ss mm HH ? * 5";
public static final String FRIDAY = "ss mm HH ? * 6";
public static final String SATURADY = "ss mm HH ? * 7";
public static String formatDateByPattern(Date date, String dateFormat) {
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
String formatTimeStr = null;
if (date != null) {
formatTimeStr = sdf.format(date);
}
return formatTimeStr;
}
/**
* 时间转换时间表达式
*/
public static String getCron(Date date, String dateFormat) {
return formatDateByPattern(date, dateFormat);
}
public static void main(String[] args) throws Exception {
String date = "14:30:00";
Date parse = DateUtil.parse(date);
String cron = getCron(parse, SUNDAY);
System.out.println("每周六执行: " + cron);
String cron1 = getCron(parse, DATEFORMAT_EVERYDAY);
System.out.println("每天执行: " + cron1);
String cron2 = getCron(new Date(), DATEFORMAT_YEAR);
System.out.println("执行一次: " + cron2);
Date date1 = DateUtils.nextTime(cron1, new Date());
System.out.println(DateUtils.parseDate(date1));
}
}
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
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
后两个是工具类可以互相转换,可以合并成一个,只是我太懒了,懒得搞。哈哈哈哈哈哈哈哈哈哈哈哈哈!
# 方案二 修改就去更新线程池 做到同步定时任务
@Profile("devt")
@Component
@Slf4j
public class ScheduleUtil implements InitializingBean, DisposableBean, ApplicationContextAware {
private final ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
//存储任务执行的包装类
private final ConcurrentHashMap<String, ScheduledFutureHolder> scheduleMap = new ConcurrentHashMap<>();
private ApplicationContext applicationContext;
@Autowired
private SpringScheduledCronMapper springScheduledCronMapper;
/**
* 启动任务
* 如果不想手动触发任务可以使用 @PostConstruct注解来启动
*/
public void startTask(String taskName, String cron) {
try {
// 判断当前任务是否已经在计划列表,在的话不再重复加载
if (scheduleMap != null && scheduleMap.containsKey(taskName)) {
this.restartTask(taskName, cron);
} else {
// 将任务交给任务调度器执行
Runnable task = (Runnable) applicationContext.getBean(Class.forName(taskName));
ScheduledFuture<?> schedule = threadPoolTaskScheduler.schedule(task, new CronTrigger(cron));
// 将任务包装成ScheduledFutureHolder
ScheduledFutureHolder scheduledFutureHolder = new ScheduledFutureHolder();
scheduledFutureHolder.setScheduledFuture(schedule);
scheduledFutureHolder.setTask(task);
scheduledFutureHolder.setCorn(cron);
scheduleMap.put(scheduledFutureHolder.getTask().getClass().getName(), scheduledFutureHolder);
log.info("定时任务启动成功,任务名:[{}],cron:[{}]", taskName, cron);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 查询所有的任务
*/
@RequestMapping("/queryTask")
public void queryTask() {
scheduleMap.forEach((k, v) -> {
System.out.println(k + " " + v);
});
}
/**
* 停止任务
*
* @param className
*/
@RequestMapping("/stop/{className}")
public void stopTask(@PathVariable String className) {
if (scheduleMap.containsKey(className)) {//如果包含这个任务
ScheduledFuture<?> scheduledFuture = scheduleMap.get(className).getScheduledFuture();
if (scheduledFuture != null) {
scheduledFuture.cancel(true);
}
}
}
/**
* 重启任务,修改任务的触发时间
*
* @param taskName,cron
* @throws InstantiationException
* @throws IllegalAccessException
*/
public void restartTask(String taskName, String cron) {
ScheduledFutureHolder scheduledFutureHolder = scheduleMap.get(taskName);
ScheduledFuture<?> scheduledFuture = scheduledFutureHolder.getScheduledFuture();
if (scheduledFuture != null) {
// 先停掉任务
scheduledFuture.cancel(true);
// 修改触发时间重新启动任务
Runnable runnable = scheduledFutureHolder.getTask();
ScheduledFuture<?> schedule = threadPoolTaskScheduler.schedule(runnable, new CronTrigger(cron));
scheduledFutureHolder.setScheduledFuture(schedule);
scheduledFutureHolder.setCorn(cron);
scheduleMap.put(scheduledFutureHolder.getTask().getClass().getName(), scheduledFutureHolder);
log.info("定时任务重新配置成功成功,任务名:[{}],cron:[{}]", taskName, cron);
}
}
@Override
public void destroy() {
threadPoolTaskScheduler.shutdown();
}
@Override
public void afterPropertiesSet() {
threadPoolTaskScheduler.setPoolSize(10);
threadPoolTaskScheduler.setThreadNamePrefix("schedule-util-");
threadPoolTaskScheduler.afterPropertiesSet();
try {
for (SpringScheduledCron springScheduledCron : springScheduledCronMapper.selectList(null)) {
if (springScheduledCron.getStatus() != 0) {
this.startTask(springScheduledCron.getCronKey(), springScheduledCron.getCronExpression());
}
}
} catch (Exception exception) {
throw new BizException(ResultCode.INTERNAL_ERROR, exception.getMessage());
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
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
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
# ScheduledFutureHolder
public class ScheduledFutureHolder {
private ScheduledFuture<?> scheduledFuture;
private Runnable task;
private String corn;
public ScheduledFuture<?> getScheduledFuture() {
return scheduledFuture;
}
public void setScheduledFuture(ScheduledFuture<?> scheduledFuture) {
this.scheduledFuture = scheduledFuture;
}
public String getCorn() {
return corn;
}
public void setCorn(String corn) {
this.corn = corn;
}
public Runnable getTask() {
return task;
}
public void setTask(Runnable task) {
this.task = task;
}
@Override
public String toString() {
return "ScheduledFutureHolder{" +
"scheduledFuture=" + scheduledFuture +
", task=" + task +
", corn='" + corn + '\'' +
'}';
}
}
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
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
上次更新: 2023/07/03, 11:02:01