SpringBoot基础-Quartz定时任务单点持久化

本文介绍了一种基于Quartz框架实现的单点持久化定时任务管理系统。系统通过RESTful风格API实现了定时任务的增删改查操作,并使用Swagger进行接口测试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1 概述

  实际项目中,复杂的定时任务都会结合持久化,动态改变定时任务状态,本文将介绍基于Quartz的定时任务单点持久化方式,通过RESTful风格,演示定时任务的CRUD,最后使用Swagger测试。

  2 数据库表说明

  //Quartz表

  qrtz_calendars:以 Blob 类型存储 Quartz 的 Calendar 信息

  qrtz_cron_triggers:存储 Cron Trigger,包括 Cron 表达式和时区信息

  qrtz_fired_triggers:存储与已触发的 Trigger 相关的状态信息,以及相联 Job 的执行信息

  qrtz_paused_trigger_grps:存储已暂停的 Trigger 组的信息

  qrtz_scheduler_state:存储少量的有关调度器 (Scheduler) 的状态,和别的 调度器 (Scheduler)实例(假如是用于一个集群中)

  qrtz_locks:储程序的非观锁的信息(假如使用了悲观锁)

  qrtz_job_details:存储每一个已配置的 Job 的详细信息(jobDetail)

  qrtz_job_listeners:存储有关已配置的 Job 监听器 的信息

  qrtz_simple_triggers:存储简单的 Trigger,包括重复次数,间隔,以及已触的次数

  qrtz_blog_triggers:以 Blob 类型存储的Trigger(用于 Quartz 用户用 JDBC 创建他们自己定制的 Trigger 类型,JobStore 并不知道如何存储实例的时候)

  qrtz_trigger_listeners:存储已配置的触发器监听器 ( Trigger Listener ) 的信息

  qrtz_triggers:存储已配置的 触发器 (Trigger) 的信息

  //新建表

  ScheduleJob:自定义定时任务详细状态表,方便管理定时任务

  建表脚本:/resourecs/quartz.sql

  3 添加依赖

  

  

  org.springframework.boot

  spring-boot-starter-quartz

  

  

  

  mysql

  mysql-connector-java

  runtime

  

  

  com.alibaba

  druid-spring-boot-starter

  1.1.9

  

  

  org.mybatis.spring.boot

  mybatis-spring-boot-starter

  1.3.2

  

  4 添加配置

  #数据库连接池配置

  spring:

  datasource:

  name: mysql_test

  type: com.alibaba.druid.pool.DruidDataSource

  #druid相关配置

  druid:

  #监控统计拦截的filters

  filters: stat

  driver-class-name: com.mysql.jdbc.Driver

  #基本属性

  url: jdbc:mysql://127.0.0.1:3306/springboot?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true

  username: root

  password: 123456

  #配置初始化大小/最小/最大

  initial-size: 1

  min-idle: 1

  max-active: 20

  #获取连接等待超时时间

  max-wait: 60000

  #间隔多久进行一次检测,检测需要关闭的空闲连接

  time-between-eviction-runs-millis: 60000

  #一个连接在池中最小生存的时间

  min-evictable-idle-time-millis: 300000

  validation-query: SELECT 'x'

  test-while-idle: true

  test-on-borrow: false

  test-on-return: false

  #打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false

  pool-prepared-statements: false

  max-pool-prepared-statement-per-connection-size: 20

  #Quartz配置

  quartz:

  jdbc:

  initialize-schema: always

  job-store-type: jdbc

  ##Mybatis配置

  mybatis:

  #Mapper.xml所在的位置

  mapper-locations: classpath:mapping/*.xml

  #entity扫描的包名

  type-aliases-package: com.mkeeper.entity

  5 Spring接管Quartz

  @Component

  public class ScheduleJobFactory extends AdaptableJobFactory {

  // 让不受spring管理的类具有spring自动注入的特性

  @Autowired

  private AutowireCapableBeanFactory autowireCapableBeanFactory;

  @Override

  protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {

  Object jobInstance=super.createJobInstance(bundle);

  autowireCapableBeanFactory.autowireBean(jobInstance);

  return jobInstance;

  }

  }

  6 Quartz配置SchedulerFactoryBean初始化

  @Configuration

  public class ScheduleConfig {

  @Autowired

  private ScheduleJobFactory scheduleJobFactory;

  @Bean

  @Qualifier("scheduleBean")

  public SchedulerFactoryBean schedulerFactoryBean(@Qualifier("dataSource") DataSource dataSource) {

  SchedulerFactoryBean schedulerFactoryBean=new SchedulerFactoryBean();

  // 名称

  schedulerFactoryBean.setSchedulerName("TASK_EXECUTOR");

  // 延迟10秒启动Scheduler

  schedulerFactoryBean.setStartupDelay(10);

  // 通过applicationContextSchedulerContextKey属性配置spring上下文

  schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContextKey");

  // 设置是否任意一个已定义的Job会覆盖现有的Job。默认为false,即已定义的Job不会覆盖现有的Job。

  schedulerFactoryBean.setOverwriteExistingJobs(true);

  // 自动开始

  schedulerFactoryBean.setAutoStartup(true);

  // 数据源

  schedulerFactoryBean.setDataSource(dataSource);

  // 将JobFactory改为自定义的,否则在 Job 中注入 Bean 会失败

  schedulerFactoryBean.setJobFactory(scheduleJobFactory);

  return schedulerFactoryBean;

  }

  }

  7 自定义任务管理

  实体

  @Data

  public class ScheduleJob implements Serializable {

  private static final Long serialVersionUID=1435515995276255188L;

  private Long id;

  private String className;

  private String cronExpression;

  private String jobName;

  private String jobGroup;

  private String triggerName;

  private String triggerGroup;

  private Boolean pause;

  private Boolean enable;

  private String description;

  private Date createTime;

  private Date lastUpdateTime;

  }

  为了节约篇幅,mapping,dao省略,请参考源码

  9 创建Quartz任务调度工具类(重点)

  @Slf4j

  public class ScheduleUtil {

  /**

  * 获取 Trigger Key

  *

  * @param scheduleJob

  * @return

  */

  public static TriggerKey getTriggerKey(ScheduleJob scheduleJob) {

  return TriggerKey.triggerKey(scheduleJob.getTriggerName(), scheduleJob.getTriggerGroup());

  }

  /**

  * 获取 Job Key

  *

  * @param scheduleJob

  * @return

  */

  public static JobKey getJobKey(ScheduleJob scheduleJob) {

  return JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());

  }

  /**

  * 获取 Cron Trigger

  *

  * @param scheduler

  * @param scheduleJob

  * @return

  * @throws ServiceException

  */

  public static CronTrigger getCronTrigger(Scheduler scheduler, ScheduleJob scheduleJob) throws ServiceException {

  try {

  return (CronTrigger) scheduler.getTrigger(getTriggerKey(scheduleJob));

  } catch (SchedulerException e) {

  throw new ServiceException("Get Cron trigger failed", e);

  }

  }

  /**

  * 创建任务

  *

  * @param scheduler

  * @param scheduleJob

  * @throws ServiceException

  */

  public static void createScheduleJob(Scheduler scheduler, ScheduleJob scheduleJob) throws ServiceException {

  validateCronExpression(scheduleJob);

  try {

  // 要执行的 Job 的类

  Class jobClass=(Class) Class.forName(scheduleJob.getClassName()).newInstance().getClass();

  JobDetail jobDetail=JobBuilder.newJob(jobClass)

  .withIdentity(scheduleJob.getJobName(), scheduleJob.getJobGroup())

  .withDescription(scheduleJob.getDescription())

  .build();

  CronScheduleBuilder scheduleBuilder=CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression())

  .withMisfireHandlingInstructionDoNothing();

  CronTrigger cronTrigger=TriggerBuilder.newTrigger()

  .withIdentity(scheduleJob.getTriggerName(), scheduleJob.getTriggerGroup())

  .withDescription(scheduleJob.getDescription())

  .withSchedule(scheduleBuilder)

  .startNow()

  .build();

  scheduler.scheduleJob(jobDetail, cronTrigger);

  log.info("Create schedule job {}-{} success", scheduleJob.getJobGroup(), scheduleJob.getJobName());

  if (scheduleJob.getPause()) {

  pauseJob(scheduler, scheduleJob);

  }

  } catch (Exception e) {

  e.printStackTrace();

  log.error("Execute schedule job failed");

  throw new ServiceException("Execute schedule job failed", e);

  }

  }

  /**

  * 更新任务

  *

  * @param scheduler

  * @param scheduleJob

  * @throws ServiceException

  */

  public static void updateScheduleJob(Scheduler scheduler, ScheduleJob scheduleJob) throws ServiceException {

  validateCronExpression(scheduleJob);

  try {

  TriggerKey triggerKey=getTriggerKey(scheduleJob);

  CronScheduleBuilder cronScheduleBuilder=CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression())

  .withMisfireHandlingInstructionDoNothing();

  CronTrigger cronTrigger=getCronTrigger(scheduler, scheduleJob);

  cronTrigger=cronTrigger.getTriggerBuilder()

  .withIdentity(triggerKey)

  .withDescription(scheduleJob.getDescription())

  .withSchedule(cronScheduleBuilder).build();

  scheduler.rescheduleJob(triggerKey, cronTrigger);

  log.info("Update schedule job {}-{} success", scheduleJob.getJobGroup(), scheduleJob.getJobName());

  if (scheduleJob.getPause()) {

  pauseJob(scheduler, scheduleJob);

  }

  } catch (SchedulerException e) {

  e.printStackTrace();

  log.error("Update schedule job failed");

  throw new ServiceException("Update schedule job failed", e);

  }

  }

  /**

  * 执行任务

  *

  * @param scheduler

  * @param scheduleJob

  * @throws ServiceException

  */

  public static void run(Scheduler scheduler, ScheduleJob scheduleJob) throws ServiceException {

  try {

  scheduler.triggerJob(getJobKey(scheduleJob));

  log.info("Run schedule job {}-{} success", scheduleJob.getJobGroup(), scheduleJob.getJobName());

  } catch (SchedulerException e) {

  e.printStackTrace();

  log.error("Run schedule job failed");

  throw new ServiceException("Run schedule job failed", e);

  }

  }

  /**

  * 暂停任务

  *

  * @param scheduler

  * @param scheduleJob

  */

  public static void pauseJob(Scheduler scheduler, ScheduleJob scheduleJob) throws ServiceException {

  try {

  scheduler.pauseJob(getJobKey(scheduleJob));

  log.info("Pause schedule job {}-{} success", scheduleJob.getJobGroup(), scheduleJob.getJobName());

  } catch (SchedulerException e) {

  e.printStackTrace();

  log.error("Pause schedule job failed");

  throw new ServiceException("Pause job failed", e);

  }

  }

  /**

  * 继续执行任务

  *

  * @param scheduler

  * @param scheduleJob

  * @throws ServiceException

  */

  public static void resumeJob(Scheduler scheduler, ScheduleJob scheduleJob) throws ServiceException {

  try {

  scheduler.resumeJob(getJobKey(scheduleJob));

  log.info("Resume schedule job {}-{} success", scheduleJob.getJobGroup(), scheduleJob.getJobName());

  } catch (SchedulerException e) {

  e.printStackTrace();

  log.error("Resume schedule job failed");

  throw new ServiceException("Resume job failed", e);

  }

  }

  /**

  * 删除任务

  *

  * @param scheduler

  * @param scheduleJob

  * @throws ServiceException

  */

  public static void deleteJob(Scheduler scheduler, ScheduleJob scheduleJob) throws ServiceException {

  try {

  scheduler.deleteJob(getJobKey(scheduleJob));

  log.info("Delete schedule job {}-{} success", scheduleJob.getJobGroup(), scheduleJob.getJobName());

  } catch (SchedulerException e) {

  e.printStackTrace();

  log.error("Delete schedule job failed");

  throw new ServiceException("Delete job failed", e);

  }

  }

  /**

  * 校验Cron表达式

  */

  public static void validateCronExpression(ScheduleJob scheduleJob) throws ServiceException {

  if (!CronExpression.isValidExpression(scheduleJob.getCronExpression())) {

  throw new ServiceException(String.format("Job %s expression %s is not correct!", scheduleJob.getClassName(), scheduleJob.getCronExpression()));

  }

  }

  }

  10 创建定时任务服务类

  @Service

  public class JobService {

  @Resource

  private JobMapper jobMapper;

  @Resource

  private Scheduler scheduler;

  public List getAllEnableJob() {

  return jobMapper.getAllEnableJob();

  }

  public ScheduleJob select(Long jobId) throws ServiceException {

  ScheduleJob scheduleJob=jobMapper.select(jobId);

  if (scheduleJob==null) {

  throw new ServiceException("ScheduleJob:" + jobId + " not found");

  }

  return scheduleJob;

  }

  @Transactional(rollbackFor=DataAccessException.class)

  public ScheduleJob update(Long jobId, ScheduleJob scheduleJob) throws ServiceException {

  if (jobMapper.update(scheduleJob) <=0) {

  throw new ServiceException("Update product:" + jobId + "failed");

  }

  ScheduleUtil.updateScheduleJob(scheduler, scheduleJob);

  return scheduleJob;

  }

  @Transactional(rollbackFor=DataAccessException.class)

  public boolean add(ScheduleJob scheduleJob) throws ServiceException {

  Integer num=jobMapper.insert(scheduleJob);

  if (num <=0) {

  throw new ServiceException("Add product failed");

  }

  ScheduleUtil.createScheduleJob(scheduler, scheduleJob);

  return true;

  }

  @Transactional(rollbackFor=DataAccessException.class)

  public boolean delete(Long jobId) throws ServiceException {

  ScheduleJob scheduleJob=select(jobId);

  Integer num=jobMapper.delete(jobId);

  if (num <=0) {

  throw new ServiceException("Delete product:" + jobId + "failed");

  }

  ScheduleUtil.deleteJob(scheduler, scheduleJob);

  return true;

  }

  public List getAllJob() {

  return jobMapper.getAllJob();

  }

  public boolean resume(Long jobId) throws ServiceException {

  ScheduleJob scheduleJob=updateScheduleJobStatus(jobId, false);

  ScheduleUtil.resumeJob(scheduler, scheduleJob);

  return true;

  }

  public boolean pause(Long jobId) throws ServiceException {

  ScheduleJob scheduleJob=updateScheduleJobStatus(jobId, true);

  ScheduleUtil.pauseJob(scheduler, scheduleJob);

  return true;

  }

  public boolean run(Long jobId) throws ServiceException {

  ScheduleJob scheduleJob=updateScheduleJobStatus(jobId, false);

  ScheduleUtil.run(scheduler, scheduleJob);

  return true;

  }

  private ScheduleJob updateScheduleJobStatus(Long jobId, Boolean isPause) throws ServiceException {

  ScheduleJob scheduleJob=select(jobId);

  scheduleJob.setPause(isPause);

  update(scheduleJob.getId(), scheduleJob);

  return scheduleJob;

  }

  }

  11 创建应用启动监听类

  /**

  * 启动应用时运行定时任务

  *

  * @author mkeeper

  * @create 2018/10/19 10:05

  */

  @Slf4j

  @Component

  public class ApplicationListener implements CommandLineRunner {

  @Resource

  private JobService jobService;

  @Resource

  private Scheduler scheduler;

  @Override

  public void run(String... args) {

  List scheduleJobList=jobService.getAllEnableJob();

  for (ScheduleJob scheduleJob : scheduleJobList) {

  try {

  CronTrigger cronTrigger=ScheduleUtil.getCronTrigger(scheduler, scheduleJob);

  if (cronTrigger==null) {

  ScheduleUtil.createScheduleJob(scheduler, scheduleJob);

  } else {

  ScheduleUtil.updateScheduleJob(scheduler, scheduleJob);

  }

  log.info("Startup {}-{} success", scheduleJob.getJobGroup(), scheduleJob.getJobName());

  } catch (ServiceException e) {

  log.error("Job ERROR", e);

  }

  }

  }

  }

  12 新建任务

  @Slf4j

  @Component

  public class TestJob implements Job {

  @Override

  public void execute(JobExecutionContext jobExecutionContext){

  // Do what you want here

  log.info("Test job is executing at: " + System.currentTimeMillis()/1000);

  }

  }

  13 Controller

  @RestController

  @RequestMapping("/job")

  public class JobController {

  @Autowired

  private JobService jobService;

  @GetMapping

  public R getAllJob() {

  return R.isOk().data(jobService.getAllJob());

  }

  @GetMapping("/{id}")

  public R getJob(@PathVariable("id") Long jobId) throws ServiceException {

  return R.isOk().data(jobService.select(jobId));

  }

  @PutMapping("/update/{id}")

  public R updateJob(@PathVariable("id") Long jobId, @RequestBody ScheduleJob newScheduleJob) throws ServiceException {

  return R.isOk().data(jobService.update(jobId, newScheduleJob));

  }

  @DeleteMapping("/delete/{id}")

  public R deleteJob(@PathVariable("id") Long jobId) throws ServiceException {

  return R.isOk().data(jobService.delete(jobId));

  }

  @PostMapping("/add")

  public R saveJob(@RequestBody ScheduleJob newScheduleJob) throws ServiceException {

  return R.isOk().data(jobService.add(newScheduleJob));

  }

  @GetMapping("/run/{id}")

  public R runJob(@PathVariable("id") Long jobId) throws ServiceException {

  return R.isOk().data(jobService.run(jobId));

  }

  @GetMapping("/pause/{id}")

  public R pauseJob(@PathVariable("id") Long jobId) throws ServiceException {

  return R.isOk().data(jobService.pause(jobId));

  }

  @GetMapping("/resume/{id}")

  public R resumeJob(@PathVariable("id") Long jobId) throws ServiceException {

  return R.isOk().data(jobService.resume(jobId));

  }

  }

  14 测试结果

  考虑到要测试的接口很多,这里推荐Swagger Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化RESTful风格的Web服务 添加依赖

  

  

  io.springfox

  springfox-swagger2

  2.6.1

  

  

  io.springfox

  springfox-swagger-ui

  2.6.1

  

  添加配置文

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值