springboot整合quartz定时任务( JDBC作业存储方式)

springboot整合quartz定时任务( JDBC作业存储方式)

一、介绍

Quartz提供两种基本作业存储类型:RAMJobStore、JDBC作业存储

  1. RAMJobStore
    RAM也就是内存,默认情况下Quartz会将任务调度存在内存中,这种方式性能是最好的,因为内存的速度是最快的。不好的地方就是数据缺乏持久性,但程序崩溃或者重新发布的时候,所有运行信息都会丢失。
    2、JDBC作业存储
    存到数据库之后,可以做单点也可以做集群,当任务多了之后,可以统一进行管理。关闭或者重启服务器,运行的信息都不会丢失。缺点就是运行速度快慢取决于连接数据库的快慢。

本博客使用jdbc作业存储的方式,同时实现restFul请求对任务启停实现控制,当前应用springboot版本2.4.4。

二、搭建工程

  • pom文件

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>

    <groupId>org.examplegroupId>
    <artifactId>quartz-jobartifactId>
    <version>1.0-SNAPSHOTversion>

    <properties>
        <maven.compiler.source>8maven.compiler.source>
        <maven.compiler.target>8maven.compiler.target>
    properties>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.4.4version>
        <relativePath/> 
    parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-batchartifactId>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>8.0.16version>
        dependency>
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>1.1.10version>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>
        <dependency>
             <groupId>org.springframework.bootgroupId>
             <artifactId>spring-boot-starter-quartzartifactId>
         dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>2.1.4version>
        dependency>

        <dependency>
            <groupId>com.github.pagehelpergroupId>
            <artifactId>pagehelperartifactId>
            <version>4.1.0version>
        dependency>
        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
        <dependency>
            <groupId>org.springframework.batchgroupId>
            <artifactId>spring-batch-testartifactId>
            <scope>testscope>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-cacheartifactId>
        dependency>
       <dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjrtartifactId>
        dependency>
        <dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjweaverartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-aopartifactId>
        dependency>
        <dependency>
            <groupId>org.xerialgroupId>
            <artifactId>sqlite-jdbcartifactId>
            <version>3.32.3.2version>
        dependency>
        <dependency>
            <groupId>cn.hutoolgroupId>
            <artifactId>hutool-allartifactId>
            <version>5.7.13version>
        dependency>
    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombokgroupId>
                            <artifactId>lombokartifactId>
                        exclude>
                    excludes>
                configuration>
            plugin>
        plugins>
    build>

project>
  • application.yml及application-dev.yml配置文件

application.yml

server:
  port: 8080
spring:
  profiles:
    active: dev
#数据库配置mysql数据库
syx:
  database:
    url: jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
    driver: com.mysql.cj.jdbc.Driver
    userName: xxxx
    passwd: xxx
    validationQuery: select 1 from dual
    initSize: 3
    minIdle: 3
    maxActive: 10
    checkTable: false
    maxWaitTime: 60000
    evictionRunTime: 60000
    minEvictionIdleTime: 300000
    keepAlive: true
    testWhileIdle: true
    testOnBorrow: true
    testOnReturn: false
    poolPreparedStatement: true
    maxPoolPreparedStatementSizePerConnection: 20
    validateQueryTimeOut: 3

application-dev.yml

mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml  #mapper文件的路径
#showSql
logging:
  level:
    com:
      syx:
        dao: debug
  • 数据库配置类,主要是为了绑定application.yml中的数据库配置
package com.syx.microapplication.base;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @date: 2022-03-10 23:04
 * 

数据源配置参数

*/
@Component @ConfigurationProperties(prefix = "syx.database") public class DataSourceConfigProperty { private String url; private String driver; private String userName; private String passwd; private String validationQuery = "select 1 from dual"; //连接池初始大小 private String initSize = "3"; //最小连接数 private String minIdle = "3"; //最大连接数 private String maxActive ="10"; //是否开启表的初始化检查 private boolean checkTable = false; //获取连接的最大等待时间单位为毫秒 private long maxWaitTime; //空闲检测时间单位为毫秒 private long evictionRunTime; //连接在池中最小生存时间单位毫秒 private long minEvictionIdleTime; // private boolean keepAlive; private boolean testWhileIdle; //获取连接时是否检测连接有效 private boolean testOnBorrow; //连接归还时是否检测连接有效 private boolean testOnReturn; //是否缓存PrepareStatement private boolean poolPreparedStatement; //每个连接的 PrepareStatement缓存大小 private int maxPoolPreparedStatementSizePerConnection; //连接检测超时设置 private int validateQueryTimeOut; //getters and setters.... }
  • 应用配置
package com.syx.microapplication.base;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @date: 2022-03-11 00:15
 * 

*/
@Configuration @EnableAspectJAutoProxy @ComponentScan(AppConfig.COMPONENT_SCAN) @MapperScan(AppConfig.MAPPER_SCAN) public class AppConfig { //包扫描路径 protected static final String COMPONENT_SCAN = "com.syx"; //mybatis的dao路径 protected static final String MAPPER_SCAN = "com.syx.**.dao"; }
  • 数据源配置,本应用使用的是druid数据源
package com.syx.microapplication.base;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;

/**
 * @date: 2022-03-10 23:01
 * 

数据源配置

*/
@Configuration @EnableTransactionManagement @ConditionalOnClass(DataSource.class) public class DataSourceConfig { @Autowired private DataSourceConfigProperty dataSourceConfigProperty; @Bean @Primary public DruidDataSource dataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setUrl(dataSourceConfigProperty.getUrl()); druidDataSource.setDriverClassName(dataSourceConfigProperty.getDriver()); druidDataSource.setUsername(dataSourceConfigProperty.getUserName()); druidDataSource.setPassword(dataSourceConfigProperty.getPasswd()); druidDataSource.setInitialSize(Integer.parseInt(dataSourceConfigProperty.getInitSize())); druidDataSource.setMinIdle(Integer.parseInt(dataSourceConfigProperty.getMinIdle())); druidDataSource.setMaxActive(Integer.parseInt(dataSourceConfigProperty.getMaxActive())); druidDataSource.setMaxWait(dataSourceConfigProperty.getMaxWaitTime()); druidDataSource.setTimeBetweenEvictionRunsMillis(dataSourceConfigProperty.getEvictionRunTime()); druidDataSource.setMinEvictableIdleTimeMillis(dataSourceConfigProperty.getMinEvictionIdleTime()); druidDataSource.setTestWhileIdle(dataSourceConfigProperty.isTestWhileIdle()); druidDataSource.setTestOnBorrow(dataSourceConfigProperty.isTestOnBorrow()); druidDataSource.setTestOnReturn(dataSourceConfigProperty.isTestOnReturn()); druidDataSource.setValidationQuery(dataSourceConfigProperty.getValidationQuery()); return druidDataSource; } /** * druid监控平台 * @return */ @Bean public ServletRegistrationBean<StatViewServlet> servletRegistrationBean(){ ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(); StatViewServlet servlet = new StatViewServlet(); registrationBean.setServlet(servlet); registrationBean.setName("druidView"); registrationBean.setLoadOnStartup(2); List<String> urlMapping = new ArrayList<>(); urlMapping.add("/druid/*"); registrationBean.setUrlMappings(urlMapping); return registrationBean; } }
  • quartz核心配置类QuartzConfig,该类主要是往spring容器中注入SchedulerFactoryBean,该类是quartz调度器的核心类。同时加载quartz.properyies配置文件,及维护trigger应用的xml配置文件,定义在configJobs目录下
package com.syx.microapplication.base;

import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import javax.sql.DataSource;
import java.util.List;
import java.util.Set;

/**
 * @date: 2022-03-10 22:28
 * 

* *

*/
@Configuration @AutoConfigureAfter(DataSource.class) @ImportResource(value = { "classpath*:configJobs/*ApplicationContext.xml" }) @PropertySource(value = { "classpath:quartz.properties" }) public class QuartzConfig { private static final Logger LOGGER = LoggerFactory.getLogger(QuartzConfig.class); @Bean public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource,QuartzTriggers quartzTriggers){ SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(){ @Override public void afterPropertiesSet() throws Exception { super.afterPropertiesSet(); //项目启动时打印任务信息 triggerMsgPrint(super.getScheduler()); } }; //配置数据源 schedulerFactoryBean.setDataSource(dataSource); //设置quartz.properties schedulerFactoryBean.setConfigLocation(new ClassPathResource("quartz.properties")); //设置任务自启动 schedulerFactoryBean.setAutoStartup(true); //设置调度器名称 schedulerFactoryBean.setSchedulerName("SYX_SCHEDULER"); //triggers维护在configJobs/*ApplicationContext.xml的文件中 schedulerFactoryBean.setTriggers(quartzTriggers.getTriggers()); schedulerFactoryBean.setOverwriteExistingJobs(true); return schedulerFactoryBean; } /** * 打印trigger信息 * @param scheduler */ private void triggerMsgPrint(Scheduler scheduler) throws SchedulerException { List<String> groupNames = scheduler.getTriggerGroupNames(); for (String groupName : groupNames) { Set<TriggerKey> triggerKeys = scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals(groupName)); for (TriggerKey triggerKey : triggerKeys) { Trigger trigger = scheduler.getTrigger(triggerKey); JobDetail jobDetail = scheduler.getJobDetail(trigger.getJobKey()); //在使用java配置job时需要指定key为JobName LOGGER.info("任务名->{}",jobDetail.getJobDataMap().getString("JobName")); LOGGER.info("任务描述->{}",jobDetail.getDescription()); //需要指定 默认指定为0 LOGGER.info("重复执行次数->{}",jobDetail.getJobDataMap().getString("RepeatTime")); //需要指定 默认为10ms 维护在JobDataMap LOGGER.info("重跑时间间隔->{}",jobDetail.getJobDataMap().getString("RepeatInterval")); if(trigger instanceof CronTrigger){ CronTrigger temp = (CronTrigger) trigger; LOGGER.info("cron表达式->{}",temp.getCronExpression()); LOGGER.info("开始执行时间->{}",temp.getStartTime()); } if(trigger instanceof SimpleTrigger){ SimpleTrigger temp = (SimpleTrigger) trigger; LOGGER.info("触发时间->{}",temp.getFinalFireTime()); LOGGER.info("开始执行时间->{}",temp.getStartTime()); } LOGGER.info("下次执行时间->{}",trigger.getNextFireTime()); } } } }
  • QuartzTriggers配置类与configJobs/*ApplicationContext.xml对应,用于维护triggers
package com.syx.microapplication.base;
import org.quartz.Trigger;
import java.util.List;
/**
 * @date: 2022-03-11 20:49
 * 

维护triggers

*/
public class QuartzTriggers { private List<Trigger> triggers; public Trigger[] getTriggers(){ Trigger[] arr = new Trigger[this.triggers.size()]; for (int i = 0; i < this.triggers.size(); i++) { arr[i] = this.triggers.get(i); } return arr; } public void setTriggers(List<Trigger> triggers) { this.triggers = triggers; } }


 
    <import resource="classpath*:configJobs/jobs/*ApplicationContext.xml"/>
    
    <bean id="triggerCollection" class="com.syx.microapplication.base.QuartzTriggers">
        <property name="triggers">
            <list>
                <ref bean="TestJobTrigger"/>
            list>
        property>
    bean>
beans>
  • 启动类,启动类中我们要排除springboot的quartz自动配置,进而使用我们自定义的SchedulerFactoryBean 的配置。查看QuartzAutoConfiguration源码亦可知,该类的主要作用为,往容器中注入了SchedulerFactoryBean。
package com.syx.microapplication;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
/**
 - @date: 2022-03-10 23:49
 - 

*/
@SpringBootApplication(exclude = {QuartzAutoConfiguration.class}) public class MainApp { public static void main(String[] args) { SpringApplication.run(MainApp.class,args); } }

三、新建 job

  • 新建测试job该任务必须实现org.quartz.Job接口。
package com.syx.demon.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
 * @date: 2022-03-11 21:53
 * 

*/
public class TestJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("Hello world..."); } }
  • 新建job对应的xml(这一步非常关键)

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="TestJob" class="com.syx.demon.job.TestJob"/>
    
    <bean id="TestJobName" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <property name="description" value="TestJob测试"/>
        <property name="group" value="TestJobGroup"/>
        <property name="jobClass" value="com.syx.demon.job.TestJob"/>
        <property name="durability" value="true"/>
        <property name="jobDataAsMap">
            <map>
                <entry key="JobName" value="TestJob"/>
                <entry key="RepeatTime" value="0"/>
                <entry key="RepeatInterval" value="10"/>
            map>
        property>
    bean>

    <bean id="TestJobTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="TestJobName"/>
        <property name="cronExpression" value="*/30 * * * * ?"/>
        
        <property name="misfireInstruction" value="2"/>
    bean>
beans>

四、启动工程

控制台打印了任务信息,同时任务也执行并输出了hello world…
springboot整合quartz定时任务( JDBC作业存储方式)_第1张图片

数据库中记录了任务名,任务组及任务的class信息。
springboot整合quartz定时任务( JDBC作业存储方式)_第2张图片

五、quartz扩展,通过http请求控制任务启停。

  • 新建quartz任务调度控制service
package com.syx.microapplication.system.service;

import com.syx.microapplication.system.bean.QuartzJobDetail;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * @date: 2022-03-12 16:20
 * 

*/
@Service public class QuartzSchedulerService { @Autowired private Scheduler scheduler; private static final Logger LOGGER = LoggerFactory.getLogger(QuartzSchedulerService.class); /** * 查询所有任务 * @return * @throws SchedulerException */ public List<QuartzJobDetail> getJobDetail() throws SchedulerException { List<QuartzJobDetail> result = new ArrayList<>(); List<String> groupNames = scheduler.getJobGroupNames(); for (String groupName : groupNames) { Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName)); for (JobKey jobKey : jobKeys) { JobDetail jobDetail = scheduler.getJobDetail(jobKey); QuartzJobDetail jobInfo = new QuartzJobDetail(); jobInfo.setSchedulerName(scheduler.getSchedulerName()); jobInfo.setSchedulerInstanceId(scheduler.getSchedulerInstanceId()); jobInfo.setJobName(jobKey.getName()); jobInfo.setDescription(jobDetail.getDescription()); jobInfo.setDurability(jobDetail.isDurable()); jobInfo.setRecover(jobDetail.requestsRecovery()); jobInfo.setGroup(jobKey.getGroup()); jobInfo.setJobClass(jobDetail.getJobClass().getName()); List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey); if(triggers!=null && !triggers.isEmpty()){ for (Trigger trigger : triggers) { jobInfo.setState(scheduler.getTriggerState(trigger.getKey()).name()); jobInfo.setTriggersNum(triggers.size()); jobInfo.setNextFireTime(trigger.getNextFireTime()); } }else{ jobInfo.setState("NONE"); } result.add(jobInfo); } } return result; } /** * 删除job * @param jobName * @param jobGroup * @throws SchedulerException */ public void deleteJob(String jobName,String jobGroup) throws SchedulerException { scheduler.deleteJob(JobKey.jobKey(jobName,jobGroup)); } /** * 重启job * @param jobName * @param jobGroup * @throws SchedulerException */ public void resumeJob(String jobName,String jobGroup) throws SchedulerException { scheduler.resumeJob(JobKey.jobKey(jobName,jobGroup)); } /** * 暂停job * @param jobName * @param jobGroup * @throws SchedulerException */ public void pause(String jobName,String jobGroup) throws SchedulerException { scheduler.pauseJob(JobKey.jobKey(jobName,jobGroup)); } /** * 立即执行 * @param jobName * @param jobGroup * @throws SchedulerException */ public void triggerNow(String jobName,String jobGroup) throws SchedulerException { scheduler.triggerJob(JobKey.jobKey(jobName,jobGroup)); } }
  • 任务详情包装类
package com.syx.microapplication.system.bean;

import com.syx.utils.IDUtil;

import java.util.Date;
import java.util.Map;

/**
 * @date: 2022-03-12 15:40
 * 

quartzJob的描述信息

*/
public class QuartzJobDetail { private String description; private boolean durability; private String group; private String jobClass; private Map<String,Object> jobDataMap; private String jobName; private Date nextFireTime; private int triggersNum; private String quartzInstanceId; private String schedulerInstanceId; private String schedulerName; private boolean recover; private String state; private String uuid=IDUtil.generateId(); //getters and setters }
  • 任务调度controller
package com.syx.microapplication.system.controller;

import com.syx.microapplication.system.bean.QuartzJobDetail;
import com.syx.microapplication.system.service.QuartzSchedulerService;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * @date: 2022-03-12 15:31
 * 

*/
@RestController @RequestMapping("/quartzJob") public class QuartzJobController { @Autowired private QuartzSchedulerService quartzSchedulerService; private static final Logger LOGGER = LoggerFactory.getLogger(QuartzJobController.class); @GetMapping public List<QuartzJobDetail> getJobDetail() throws SchedulerException { return quartzSchedulerService.getJobDetail(); } /** * 删除job * @param jobName * @param jobGroup * @throws SchedulerException */ @DeleteMapping public void deleteJob(String jobName,String jobGroup) throws SchedulerException { quartzSchedulerService.deleteJob(jobName,jobGroup); LOGGER.info("删除任务{}成功",jobName); } /** * 重启job * @param jobName * @param jobGroup * @throws SchedulerException */ @GetMapping(path = "{jobName}/{jobGroup}/resume") public void resumeJob(@PathVariable(value = "jobName") String jobName, @PathVariable(value = "jobGroup") String jobGroup) throws SchedulerException { quartzSchedulerService.resumeJob(jobName,jobGroup); LOGGER.info("重启任务{}成功",jobName); } /** * 暂停job * @param jobName * @param jobGroup * @throws SchedulerException */ @GetMapping(path = "{jobName}/{jobGroup}/pause") public void pause(@PathVariable(value = "jobName") String jobName, @PathVariable(value = "jobGroup") String jobGroup) throws SchedulerException { quartzSchedulerService.pause(jobName,jobGroup); LOGGER.info("暂停任务{}成功",jobName); } /** * 立即执行 * @param jobName * @param jobGroup * @throws SchedulerException */ @GetMapping(path = "{jobName}/{jobGroup}/triggerNow") public void triggerNow(@PathVariable(value = "jobName") String jobName, @PathVariable(value = "jobGroup") String jobGroup) throws SchedulerException { quartzSchedulerService.triggerNow(jobName,jobGroup); LOGGER.info("任务{}启动成功",jobName); } }

六、总结

你可能感兴趣的:(spring,boot,spring,java)