SpringBoot Quartz 定时服务

  • Post author:
  • Post category:其他


开发环境: 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. 结语


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进行简单的认识及使用。



版权声明:本文为ceaningking原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。