开发环境: IDEA 2022.1.4+ MSSQL
SpringBoot+Mybatis+Quartz
还在搞整application.yml集成Mybatis设置以及SQL查询,暂未找到满意资料,代码候补。
代码地址: 候补
目录
2.4 Application.yml配置数据源、Quartz设置
3.4 定义任务接口QuartzJobServiceImpl
1. 概述
在项目开发中,服务后台经常用到定时服务处理数据,像前段时间做的涉医实名就诊接口,就需要定时上传数据,在当前学习SpringBoot的基础上,在网上了解下解决方案,觉得Quartz不错,就想搞一搞,在网上找到了某位大佬的源码(SpringBoot+Mybatis-Plus+Quartz 地址:
GitHub – ningzaichun/springboot-quartz: springboot-quartz
),我本地使用的是Mybatis,不大想用Mybatis-Plus,就得借鉴下大佬源码进行学习。
涉及定时任务,肯定有相关的接口参数,这类参数,就可以通过Quartz进行参数传递。最终效果如下:
本文对主要实现进行简单说明:
2. 依赖及设置
2.1 Quartz表
Quartz带有自己的表结构,这个在网上有很多,可以在网上直接查找,表的结构说明也可以在网上查找。我本地使用MSSQL,就修改了下脚本,在MSSQL里执行。效果如下:
2.2 MSSQL单独建表
方便操作测试,就在MSSQL单独建表来存储定时任务信息。
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE 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]
GO
SET ANSI_PADDING OFF
GO
2.3 Maven添加Quartz依赖
<!-- 开启quartz -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
<version>2.6.5</version>
</dependency>
2.4 Application.yml配置数据源、Quartz设置
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: jdbc
org:
quartz:
jobStore:
class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
#配置数据源
datasource:
url: jdbc:sqlserver://127.0.0.1:1433;SelectMethod=cursor;databaseName=EFMIS
username: sa
password: 123qwe,.
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
3. 代码实现
主要的类说明如下如箭头所示:
3.1 定义定时任务类参数QuartzJob
其实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;
}
3.2 定义带参任务类SysmJob
此时方便看效果,任务里直接打印当前接口参数(json)。
@Slf4j
@DisallowConcurrentExecution//定义不能同时并发执行相同的JobDetail
public class SysmJob implements Job {
@Autowired
private IQuartzJobService service;
/**
* 创建任务时候设置的json参数
*/
private String parameter;
public void setParameter(String parameter) {
this.parameter = parameter;
}
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println(CommonConstant.FMR_DATE_19.format(new Date())+ parameter);
if (1==1){
return;
}
}
}
3.3 定义任务接口IQuartzJobService
接口还是按大佬的想法来整,新增-启动、更新-启动、删除-停止。我在整理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;
}
3.4 定义任务接口QuartzJobServiceImpl
这里要注意的是,schedulerAdd方法中,一句” usingJobData(“parameter”, parameter) “, 这个参数名必须和类SysmJob的属性parameter一样。如果不想把参数整合成一个json集合,就可以使用usingJonData进行多次加入,感觉有点麻烦了,一次性加入好点。
@Slf4j
@Service
public class QuartzJobServiceImpl implements IQuartzJobService {
@Autowired
private 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
*/
@Override
public 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
*/
@Override
public 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
*/
@Override
public boolean deleteAndStopJob(QuartzJob quartzJob) {
return schedulerDelete(quartzJob.getJobclassname().trim());
}
}
3.5 定义空值类QuartzJobController
@Slf4j
@RestController
@RequestMapping("/api")
public class QuartzJobController {
@Autowired
private 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<String,List<QuartzJob>> map= new HashMap<String,List<QuartzJob>>();
List<QuartzJob> list= new ArrayList<QuartzJob>();
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]操作失败!");
}
}
}
4. 使用POSTMAN测试
4.1 新增-启动
新增任务的Cron表达式设置为一分钟一次执行
4.2 更新-启动
更新任务的Cron表达式修改为两分钟一次执行
5. 结语
对Quartz进行简单的认识及使用。