SpringBoot2.5.x整合Quartz并持久化
风风火火,恍恍惚惚,springboot2.6.0正式版也发布了,同时也宣布了2.4x版本停止维护。2.5.x系列也是目前自己写Demo在用的,为什么要记录下来呢, 主要我把项目升级到2.6之后项目跑不起来了,原因我知道,但是我不知道怎么解决,看了看机翻的更新文档,有处可能是影响我项目跑不起来的原因,就是限制了依赖的循环导入,现在依赖循环导入,项目是会直接跑不起来。我的问题是项目里获取不到DataSource数据源,我用了各种姿势,都获取不到,靓仔在线疑惑。既然不能无缝升级,那我就记下来一个正常的版本。
定时任务是很常用的一个功能,比如简单的生日提醒,每天定点查询用户的生日信息。然后发出一系列的提示操作。不了解你就感觉很难,但是现在spring给我们带来了很多的便利,看一看,在头脑风暴一下,也不难。编程一途,殊途同归。
主要工作中好多同步代码感觉都可以使用定时任务来处理,然而实际上用的是一个一个的Main方法,吐槽吧,人家那代码又不是不能跑,不仅能跑,还能赚钱。没啥好吐槽的,就是维护时想骂娘,骂娘就骂娘,还十分容易出错。
核心配置类
用了这个配置类,腰不酸了,腿不痛了,一次能爬五层楼。觉也睡的香了。好吧,前面纯属扯淡,写了这个yml中就不需要配置了,大致看一下每行的注释,测试学习中以先跑起来为主,然后再慢慢研究即可。
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
| package com.wangijun.springbootdemo.config;
import ...;
@Configuration public class QuartzConfig {
@Resource private DataSource dataSource;
@Bean public SchedulerFactoryBean schedulerFactoryBean() { Properties prop = new Properties(); prop.put("org.quartz.scheduler.instanceName", "QlScheduler"); prop.put("org.quartz.scheduler.instanceId", "AUTO"); prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); prop.put("org.quartz.threadPool.threadCount", "20"); prop.put("org.quartz.threadPool.threadPriority", "5"); prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX"); prop.put("org.quartz.jobStore.isClustered", "false"); prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000"); prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "5000"); prop.put("org.quartz.jobStore.misfireThreshold", "12000");
prop.put("org.quartz.jobStore.tablePrefix", "schedule_"); prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setDataSource(dataSource); factory.setQuartzProperties(prop); factory.setSchedulerName("QlScheduler"); factory.setStartupDelay(30); factory.setApplicationContextSchedulerContextKey("applicationContextKey"); factory.setOverwriteExistingJobs(true); factory.setAutoStartup(true); return factory; } }
|
此种配置的Quartz是持久化到数据库的,就是项目重启定时任务的状态并不会丢失。
用于Web管理Bean
可以自定义,要点看代码注释。
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
| package com.wangijun.springbootdemo.pojo;
import ...;
@Data @TableName(value = "schedule_job") public class ScheduleJob implements Serializable {
public static final String JOB_PARAM_KEY = "JOB_PARAM_KEY";
@TableId(value = "id", type = IdType.ASSIGN_ID) private String id;
@TableField(value = "bean_name") private String beanName;
@TableField(value = "params") private String params;
@TableField(value = "cron_expression") private String cronExpression;
@TableField(value = "`status`") private Byte status;
@TableField(value = "remark") private String remark;
@TableField(value = "create_time") private LocalDateTime createTime;
@TableField(value = "job_name") private String jobName;
private static final long serialVersionUID = 1L; }
|
定时任务执行日志Bean
主要就是记录一下日志,可根据自己需求,自行扩展。
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
| package com.wangijun.qluiserver.pojo;
import ...;
@Data @TableName(value = "schedule_log") public class ScheduleLog implements Serializable {
@TableId(value = "id", type = IdType.INPUT) private String id;
@TableField(value = "job_id") private String jobId;
@TableField(value = "job_name") private String jobName;
@TableField(value = "params") private String params;
@TableField(value = "`status`") private String status;
@TableField(value = "error") private String error;
@TableField(value = "times") private Integer times;
@TableField(value = "create_time") private LocalDateTime createTime;
@TableField(value = "bean_name") private String beanName;
private static final long serialVersionUID = 1L; }
|
执行定时任务的上下文
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
| package com.wangijun.springbootdemo.schedule;
import ...;
@Slf4j public class ScheduleContext extends QuartzJobBean {
@Override protected void executeInternal(JobExecutionContext context) { Object o = context.getMergedJobDataMap().get(ScheduleJob.JOB_PARAM_KEY); ScheduleJob jobBean = (ScheduleJob) o; ScheduleLogService scheduleJobLogService = (ScheduleLogService) SpringUtil.getBean("scheduleLogService"); ScheduleLog logBean = new ScheduleLog() ; logBean.setId(IdWorker.getIdStr()); logBean.setJobId(jobBean.getId()); logBean.setJobName(jobBean.getJobName()); logBean.setBeanName(jobBean.getBeanName()); logBean.setParams(jobBean.getParams()); logBean.setCreateTime(LocalDateTime.now()); long beginTime = System.currentTimeMillis() ; try { Object target = SpringUtil.getBean(jobBean.getBeanName()); Method method = target.getClass().getDeclaredMethod("run", String.class); method.invoke(target, jobBean.getParams()); long executeTime = System.currentTimeMillis() - beginTime; logBean.setTimes((int) executeTime); logBean.setStatus("0"); log.info("定时器 === >> " + jobBean.getJobName() + "执行成功,耗时 === >> " + executeTime); } catch (Exception e){ long executeTime = System.currentTimeMillis() - beginTime; logBean.setTimes((int)executeTime); logBean.setStatus("1"); logBean.setError(e.getCause().getMessage()); e.getCause().printStackTrace(); } finally { scheduleJobLogService.save(logBean); } } }
|
定时任务处理器
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
| package com.wangijun.springbootdemo.schedule;
import ...;
@NoArgsConstructor public class ScheduleHandler {
private static final String SCHEDULE_NAME = "Ql_" ;
public static TriggerKey getTriggerKey(Long jobId){ return TriggerKey.triggerKey(SCHEDULE_NAME + jobId) ; }
public static JobKey getJobKey (Long jobId){ return JobKey.jobKey(SCHEDULE_NAME+jobId) ; }
public static CronTrigger getCronTrigger (Scheduler scheduler, Long jobId){ try { return (CronTrigger)scheduler.getTrigger(getTriggerKey(jobId)) ; } catch (SchedulerException e){ return null; } }
public static void createJob (Scheduler scheduler, ScheduleJob scheduleJob){ try { JobDetail jobDetail = JobBuilder.newJob(ScheduleContext.class).withIdentity(getJobKey(Long.parseLong(scheduleJob.getId()))).build() ; CronScheduleBuilder scheduleBuilder = CronScheduleBuilder .cronSchedule(scheduleJob.getCronExpression()) .withMisfireHandlingInstructionDoNothing() ; CronTrigger trigger = TriggerBuilder.newTrigger() .withIdentity(getTriggerKey(Long.parseLong(scheduleJob.getId()))) .withSchedule(scheduleBuilder).build(); jobDetail.getJobDataMap().put(ScheduleJob.JOB_PARAM_KEY,scheduleJob); scheduler.scheduleJob(jobDetail,trigger); if ("1".equals(scheduleJob.getStatus())){ pauseJob(scheduler,Long.parseLong(scheduleJob.getId())) ; } } catch (SchedulerException e){ throw new RuntimeException("createJob Fail",e) ; } }
public static void updateJob(Scheduler scheduler, ScheduleJob scheduleJob) { try { TriggerKey triggerKey = getTriggerKey(Long.parseLong(scheduleJob.getId())); CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression()).withMisfireHandlingInstructionDoNothing(); CronTrigger trigger = getCronTrigger(scheduler, Long.parseLong(scheduleJob.getId())); assert trigger != null; trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); trigger.getJobDataMap().put(ScheduleJob.JOB_PARAM_KEY, scheduleJob); scheduler.rescheduleJob(triggerKey, trigger); if("1".equals(scheduleJob.getStatus())){ pauseJob(scheduler, Long.parseLong(scheduleJob.getId())); } } catch (SchedulerException e) { throw new RuntimeException("updateJob Fail",e); } }
public static void pauseJob (Scheduler scheduler, Long jobId){ try { scheduler.pauseJob(getJobKey(jobId)); } catch (SchedulerException e){ throw new RuntimeException("pauseJob Fail",e); } }
public static void resumeJob (Scheduler scheduler, Long jobId){ try { scheduler.resumeJob(getJobKey(jobId)); } catch (SchedulerException e){ throw new RuntimeException("resumeJob Fail",e); } }
public static void deleteJob (Scheduler scheduler, Long jobId){ try { scheduler.deleteJob(getJobKey(jobId)); } catch (SchedulerException e){ throw new RuntimeException("deleteJob Fail",e); } }
public static void run (Scheduler scheduler, ScheduleJob scheduleJob){ try { JobDataMap dataMap = new JobDataMap() ; dataMap.put(ScheduleJob.JOB_PARAM_KEY,scheduleJob); scheduler.triggerJob(getJobKey(Long.parseLong(scheduleJob.getId())),dataMap); } catch (SchedulerException e){ throw new RuntimeException("run Fail",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
| package com.wangijun.springbootdemo.schedule;
import ...;
public class ScheduleStarted {
@Resource private Scheduler scheduler ;
@Resource private ScheduleJobService scheduleJobService;
@PostConstruct public void init (){ List<ScheduleJob> scheduleJobList = scheduleJobService.list(); for (ScheduleJob scheduleJob : scheduleJobList) { CronTrigger cronTrigger = com.wangijun.springbootdemo.schedule.ScheduleHandler.getCronTrigger(scheduler,Long.parseLong(scheduleJob.getId())) ; if (cronTrigger == null){ com.wangijun.springbootdemo.schedule.ScheduleHandler.createJob(scheduler,scheduleJob); } else { com.wangijun.springbootdemo.schedule.ScheduleHandler.updateJob(scheduler,scheduleJob); } } } }
|
Web接口
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
| package com.wangijun.springbootdemo;
import ...;
@RestController public class ScheduleController { @Resource ScheduleJobService jobService; @Resource ScheduleLogService logService; @Resource Scheduler scheduler;
@GetMapping("task_run") ResponseEntity<Object> taskRun(String id) { ScheduleJob job = jobService.getById(jobId); ScheduleHandler.run(scheduler, job); return ResponseEntity.ok("成功"); }
@PostMapping("task_save") ResponseEntity<Object> taskSave(ScheduleJob job) { job.setId(IdWorker.getIdStr()); job.setCreateTime(LocalDateTime.now()); jobService.save(job); ScheduleHandler.createJob(scheduler, job); return ResponseEntity.ok("成功"); }
@PostMapping("task_update") ResponseEntity<Object> taskUpdate(ScheduleJob job) { jobService.updateById(job); ScheduleHandler.updateJob(scheduler, job); return ResponseEntity.ok("成功"); }
@GetMapping("task_pause") ResponseEntity<Object> taskPause(String id) { ScheduleJob scheduleJob = jobService.getById(jobId); ScheduleHandler.pauseJob(scheduler, Long.parseLong(jobId)); scheduleJob.setStatus("1"); jobService.updateById(scheduleJob); return ResponseEntity.ok("成功"); }
@GetMapping("task_resume") ResponseEntity<Object> taskResume(String id) { ScheduleJob scheduleJob = jobService.getById(jobId); ScheduleHandler.resumeJob(scheduler, Long.parseLong(jobId)); scheduleJob.setStatus("0"); jobService.updateById(scheduleJob); return ResponseEntity.ok("成功"); }
@GetMapping("task_del") ResponseEntity<Object> taskDel(String jobId) { ScheduleHandler.deleteJob(scheduler, Long.parseLong(jobId)); jobService.removeById(jobId); return ResponseEntity.ok("成功"); } }
|
定时任务类
新增接口接口BaseTaskService.java
1 2 3 4 5 6 7 8 9
| package com.wangijun.springbootdemo.task.base;
public interface BaseTaskService {
void run(String params) throws Exception; }
|
通用定时任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package com.wangijun.springbootdemo.task;
import ...;
@Slf4j @Component("commonTask") public class CommonTaskImpl implements BaseTaskService { private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") ;
@Override public void run(String params) { log.info("Params === >> " + params); log.info("当前时间::::" + FORMAT.format(new Date())); System.out.println("执行成功"); } }
|
异常的定时任务
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
| package com.wangijun.springbootdemo.task;
import ...;
@Slf4j @Component("exceptionTask") public class ExceptionTaskImpl implements BaseTaskService {
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") ;
@Override public void run(String params) throws Exception{ log.info("当前时间::::" + FORMAT.format(new Date())); throw new Exception("发生异常"); } }
|
后话
建表语句以及测试项目源码,mock.http文件中里面有测试数据,IDEA可以很方便的测试。
源码