开发环境: IDEA 2022.1.4+ MSSQL
SpringBoot+Mybatis+Quartz
还在搞整application.yml集成Mybatis设置以及SQL查询,暂未找到满意资料,代码候补。
代码地址: 候补
目录
1. 概述
2. 依赖及设置
2.1 Quartz表
2.2 MSSQL单独建表
2.3 Maven添加Quartz依赖
2.4 Application.yml配置数据源、Quartz设置
3. 代码实现
3.1 定义定时任务类参数QuartzJob
3.2 定义带参任务类SysmJob
3.3 定义任务接口IQuartzJobService
3.4 定义任务接口QuartzJobServiceImpl
3.5 定义空值类QuartzJobController
4. 使用POSTMAN测试
4.1 新增-启动
4.2 更新-启动
5. 结语
在项目开发中,服务后台经常用到定时服务处理数据,像前段时间做的涉医实名就诊接口,就需要定时上传数据,在当前学习SpringBoot的基础上,在网上了解下解决方案,觉得Quartz不错,就想搞一搞,在网上找到了某位大佬的源码(SpringBoot+Mybatis-Plus+Quartz),我本地使用的是Mybatis,不大想用Mybatis-Plus,就得借鉴下大佬源码进行学习。
涉及定时任务,肯定有相关的接口参数,这类参数,就可以通过Quartz进行参数传递。最终效果如下:
本文对主要实现进行简单说明:
Quartz带有自己的表结构,这个在网上有很多,可以在网上直接查找,表的结构说明也可以在网上查找。我本地使用MSSQL,就修改了下脚本,在MSSQL里执行。效果如下:
方便操作测试,就在MSSQL单独建表来存储定时任务信息。
SET ANSI_NULLS ON
GOSET QUOTED_IDENTIFIER ON
GOSET ANSI_PADDING ON
GOCREATE TABLE [dbo].[TB_QUARTZ_JOB]([id] [int] IDENTITY(1,1) NOT NULL,[createby] [varchar](32) NOT NULL,[createtime] [datetime] NOT NULL,[jobclassname] [varchar](255) NOT NULL,[cronexpression] [varchar](255) NOT NULL,[parameter] [varchar](255) NULL,[description] [varchar](255) NOT NULL,CONSTRAINT [PK_TB_QUARTZ_JOB] PRIMARY KEY CLUSTERED
([id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]GOSET ANSI_PADDING OFF
GO
org.springframework.boot spring-boot-starter-quartz 2.6.5
quartz的job-store-type设置为jdbc,设置这个后,就需要设置数据源datasource, 定时任务的设置才能保存到Quartz表中。
关于org.quartz.jonstore.class的设置有区别,spring 2.5.x之前是另一个设置,我当前是spring 2.6.5,就设置的这个。
spring:# quartz定时任务配置quartz:# 数据库存储方式job-store-type: jdbcorg:quartz:jobStore:class: org.springframework.scheduling.quartz.LocalDataSourceJobStore#配置数据源datasource:url: jdbc:sqlserver://127.0.0.1:1433;SelectMethod=cursor;databaseName=EFMISusername: sapassword: 123qwe,.driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
主要的类说明如下如箭头所示:
其实Quartz是带有基础表的,如果直接去查那些表,感觉有点麻烦。就直接新建一个表(2.2 MSSQL单独建表)来存储如下类信息。
主要说明:
1. jobclassname 是任务的全路径,比如"com.ceaning.crudp.quartz.SysmJob"(见上图结构)
2. parameter参数,我想着使用json格式来存储,毕竟一个接口对应的参数是比较多的。
/*** 服务后端定时任务类*/
@Data
public class QuartzJob {/*** id 自增*/private int id;/*** 任务类名(当主键用)*/private String jobclassname;/*** 创建人*/private String createby;/*** corn表达式*/private String cronexpression;/*** 任务参数*/private String parameter;/*** 任务描述*/private String description;/*** 创建时间*/@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date createtime;}
此时方便看效果,任务里直接打印当前接口参数(json)。
@Slf4j
@DisallowConcurrentExecution//定义不能同时并发执行相同的JobDetail
public class SysmJob implements Job {@Autowiredprivate IQuartzJobService service;/*** 创建任务时候设置的json参数*/private String parameter;public void setParameter(String parameter) {this.parameter = parameter;}@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {System.out.println(CommonConstant.FMR_DATE_19.format(new Date())+ parameter);if (1==1){return;}}}
接口还是按大佬的想法来整,新增-启动、更新-启动、删除-停止。我在整理Quartz接口时候,我发现有好些接口,比如 resume***、pause***等。 我就不想整的太复杂了, 简单点好。
public interface IQuartzJobService {/*** 新增并启动* @param quartzJob* @return*/boolean addAndScheduleJob(QuartzJob quartzJob);/*** 更新并启动* @param quartzJob* @return*/boolean updateAndScheduleJob(QuartzJob quartzJob) throws SchedulerException;/*** 删除并停止* @param quartzJob* @return*/boolean deleteAndStopJob(QuartzJob quartzJob) throws SchedulerException;
}
这里要注意的是,schedulerAdd方法中,一句" usingJobData("parameter", parameter) ", 这个参数名必须和类SysmJob的属性parameter一样。如果不想把参数整合成一个json集合,就可以使用usingJonData进行多次加入,感觉有点麻烦了,一次性加入好点。
@Slf4j
@Service
public class QuartzJobServiceImpl implements IQuartzJobService {@Autowiredprivate Scheduler scheduler;/*** 根据任务类别获取类* @param jobclassname* @return* @throws Exception*/private static Job getClass(String jobclassname) throws Exception{Class> cls= Class.forName(jobclassname);return (Job)cls.newInstance();}/*** 调度器中新增任务* @param jobclassname* @param cronexpression* @param parameter*/private boolean schedulerAdd(String jobclassname, String cronexpression, String parameter){boolean bRet= false;try{scheduler.start();JobDetail detail= JobBuilder.newJob(getClass(jobclassname).getClass()).withIdentity(jobclassname).usingJobData("parameter", parameter).build();CronScheduleBuilder scheduleBuilder= CronScheduleBuilder.cronSchedule(cronexpression);CronTrigger trigger= TriggerBuilder.newTrigger().withIdentity(jobclassname).withSchedule(scheduleBuilder).build();scheduler.scheduleJob(detail,trigger);bRet= true;} catch (SchedulerException e){e.printStackTrace();} catch (RuntimeException e){e.printStackTrace();} catch (Exception e){e.printStackTrace();}return bRet;}/*** 从调度器中删除任务* @param jobclassname*/private boolean schedulerDelete(String jobclassname){boolean bRet= false;try{scheduler.pauseTrigger(TriggerKey.triggerKey(jobclassname));scheduler.unscheduleJob(TriggerKey.triggerKey(jobclassname));scheduler.deleteJob(JobKey.jobKey(jobclassname));bRet= true;}catch (Exception e){log.error(e.getMessage(), e);}return bRet;}/*** 保存&启动定时任务* @param quartzJob* @return*/@Overridepublic boolean addAndScheduleJob(QuartzJob quartzJob) {boolean bRet= false;this.schedulerAdd(quartzJob.getJobclassname().trim(), quartzJob.getCronexpression().trim(), quartzJob.getParameter().trim());bRet= true;return bRet;}/*** 编辑&启动定时任务* @param quartzJob* @return*/@Overridepublic boolean updateAndScheduleJob(QuartzJob quartzJob) throws SchedulerException{boolean bRet= false;schedulerDelete(quartzJob.getJobclassname());bRet= schedulerAdd(quartzJob.getJobclassname().trim(), quartzJob.getCronexpression().trim(), quartzJob.getParameter());return bRet;}/*** 删除&停止定时任务* @param quartzJob* @return*/@Overridepublic boolean deleteAndStopJob(QuartzJob quartzJob) {return schedulerDelete(quartzJob.getJobclassname().trim());}}
@Slf4j
@RestController
@RequestMapping("/api")
public class QuartzJobController {@Autowiredprivate QuartzJobServiceImpl service;private boolean crud(SysEnum.crudopr opr, QuartzJob quartzJob){boolean bRet= false;int iRet= 0;SqlSession sqlSession= null;try{sqlSession= MybatisUtils.getSqlSession();QuartzJobMapper mapper= sqlSession.getMapper(QuartzJobMapper.class);switch (opr){case insert:iRet= mapper.add(quartzJob);break;case update:iRet= mapper.update(quartzJob);break;case delete:iRet= mapper.delete(quartzJob);break;default:iRet= 0;}if (iRet>0){sqlSession.commit();bRet= true;} else{sqlSession.rollback();}} catch (Exception e){e.printStackTrace();} finally {if (sqlSession!= null){sqlSession.close();}}return bRet;}@GetMapping("/task/getList")public Result> getJobList(){Map> map= new HashMap>();List list= new ArrayList();SqlSession sqlSession= null;try{sqlSession= MybatisUtils.getSqlSession();QuartzJobMapper mapper= sqlSession.getMapper(QuartzJobMapper.class);list= mapper.getList();map.put("data", list);return Result.ok(map);} catch (Exception e){e.printStackTrace();} finally {if (sqlSession!= null){sqlSession.close();}}return Result.error(CommonConstant.SC_INTERNAL_SERVER_ERROR_500,"操作失败!");}@GetMapping("/task/findById")public QuartzJob findById(int id){QuartzJob quartzJob= null;SqlSession sqlSession= null;try{sqlSession= MybatisUtils.getSqlSession();QuartzJobMapper mapper= sqlSession.getMapper(QuartzJobMapper.class);quartzJob= mapper.findById(id);} catch (Exception e){e.printStackTrace();} finally {if (sqlSession!= null){sqlSession.close();}}return quartzJob;}@PostMapping("/task/addAndScheduleJob")public Result> addAndScheduleJob(@RequestBody QuartzJob quartzJob){boolean bRet= service.addAndScheduleJob(quartzJob);if (bRet) {bRet= crud(SysEnum.crudopr.insert, quartzJob);if (bRet) {return Result.ok(quartzJob);} else {if (crud(SysEnum.crudopr.delete,quartzJob)){return Result.error("[addJob].[addAndScheduleJob]操作失败!");} else {return Result.error("[addJob].[addAndScheduleJob].[crud]操作失败!");}}} else {return Result.error("[addJob].[crud]操作失败!");}}@PostMapping("/task/updateAndScheduleJob")public Result> updateAndScheduleJob(@RequestBody QuartzJob quartzJob) throws SchedulerException{boolean bRet= false;QuartzJob job= findById(quartzJob.getId());if (job== null){return Result.error("[updateJob].[findById]无有效记录!");}//优先处理服务bRet= service.updateAndScheduleJob(quartzJob);if (bRet) {//服务启动成功后 才允许修改业务数据if (crud(SysEnum.crudopr.update, quartzJob)) {return Result.ok("操作成功!");} else {return Result.ok("[updateAndScheduleJob].[crud]操作失败!");}} else {return Result.error("[updateAndScheduleJob]操作失败!");}}@PostMapping("/task/deleteAndStopJob")public Result> deleteAndStopJob(@RequestBody QuartzJob quartzJob){boolean bRet= false;QuartzJob job= findById(quartzJob.getId());if (job== null){return Result.error("[deleteJob].[findById]无有效记录!");}//优先处理服务bRet= service.deleteAndStopJob(quartzJob);if (bRet) {//服务启动成功后 才允许删除业务数据if (crud(SysEnum.crudopr.delete, quartzJob)) {return Result.ok("操作成功!");} else {return Result.ok("[deleteJob].[deleteAndStopJob].[crud]操作失败!");}} else {return Result.error("[deleteJob].[deleteAndStopJob]操作失败!");}}}
新增任务的Cron表达式设置为一分钟一次执行
更新任务的Cron表达式修改为两分钟一次执行
对Quartz进行简单的认识及使用。
上一篇:算法的时间复杂度介绍
下一篇:i18n 国际化