Springboot+quartz整合(多数据源+quartz持久化到数据库)

谈谈

最近在公司需要做一个定时任务管理的小项目,心想采用Springboot和quartz能减少不少配置,迅速完成项目搭建和开发。以前做过SpringMVC中配置定时任务,都是在xml配置文件中配置一大堆bean来实现任务的配置与存储,所以这次也同样在pom文件中导入

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.2</version>
</dependency>

但是经过一番折腾,由于知识的浅薄,通过配置文件配置quartz持久化到数据库失败了。
无意间看到一个叫 Spring-boot-starter-quartz 的包,立马去百度查,这才知道,原来Springboot早已经集成了quartz。于是从新导入新的quartz依赖,进行一番配置后,终于大功告成。
下面简单介绍一下我的配置方式。
还是老样子,如果发现有错误或则有待提升的地方,欢迎各位大佬指出来!

导入quartz依赖

既然用springboot做项目,那咱们还是老老实实导入spring家族的依赖吧。一切为了简化配置。

<dependency>
	<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

初始化quartz持久化所需要的数据库表

官方提供了初始化数据库所需要的脚本,下载链接http://www.quartz-scheduler.org/downloads/files/quartz-2.2.3-distribution.tar.gz。不同数据库使用不同的脚本不一样,下载后通过解压路径/quartz-2.2.3/docs/dbTables/可以看到有很多类型的脚本,找到适合自己数据库的就行。我用的mysql,所以打开tables_mysql.sql,复制其中的内容到数据库工具执行一遍就ok。
Alt
运行完成后,可以看到数据库有以下表,通过表明容易理解其用途。
Alt
其中

org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate

这个配置需要根据不同数据库类型,设置到yml配置文件中

配置yml文件

这里有一部分涉及到多数据源配置,因为公司项目涉及到多种数据库和多个表的操作,所以我将quartz持久化所需要的表放到了新的数据库task下边。

spring:
  datasource:
    #数据源1
    db1:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://192.168.137.53:3306/db1?useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: mysqlpassword
      type: com.alibaba.druid.pool.DruidDataSource
    #数据源2
    db2:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://192.168.137.53:3306/db2?useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: mysqlpassword
      type: com.alibaba.druid.pool.DruidDataSource
    #quartz数据源
    task:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://192.168.137.53:3306/task?useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: mysqlpassword
      type: com.alibaba.druid.pool.DruidDataSource
  quartz:
    properties:
      org:
        quartz:
          scheduler:
            instanceName: DemoScheduler
            instanceId: AUTO
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate #跟数据库初始化脚本中配置保持一致
            tablePrefix: QRTZ_
            isClustered: true
            clusterCheckinInterval: 10000
            useProperties: false
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 20
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true
    job-store-type: jdbc
    overwrite-existing-jobs: true
    auto-startup: true

quartz数据源配置

在yml配置文件中我们配置了三个数据源,一个是task,存储持久化quartz的数据表;其他两个当作是业务数据源,可能来自不同的数据库。
多数据源配置可以参考我之前的文章Springboot+Mybatis+MySql整合多数据源及其使用
新建DataSourceConfig类,配置三个数据源,并通过@QuartzDataSource告诉spring容器quartz使用的数据源是哪一个。

@Configuration
@MapperScan(basePackages = "com.czhjsl.dao")
public class DataSourceConfig {

    @Bean(name = "dataSource1")
    @Primary
    @ConfigurationProperties("spring.datasource.db1")
    public DruidDataSource dataSource1 () {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "dataSource2")
    @ConfigurationProperties("spring.datasource.db2")
    public DruidDataSource dataSource2 () {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "dataSourceTask")
    @ConfigurationProperties("spring.datasource.task")
    @QuartzDataSource
    public DruidDataSource dataSourceTask () { return DruidDataSourceBuilder.create().build(); }

    @Bean(name = "myRoutingDataSource")
    public MyRoutingDataSource myRoutingDataSource (@Qualifier("dataSource1") DruidDataSource dataSource1,
                                                @Qualifier("dataSource2") DruidDataSource dataSource2) {
        Map<Object, Object> map = new HashMap<>();
        map.put(DataSourceType.DB1, dataSource1);
        map.put(DataSourceType.DB2, dataSource2);
        MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
        myRoutingDataSource.setTargetDataSources(map);
        myRoutingDataSource.setDefaultTargetDataSource(dataSource1);
        return myRoutingDataSource;
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory (@Qualifier("dataSource1") DruidDataSource dataSource1,
                                                @Qualifier("dataSource2") DruidDataSource dataSource2) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(myRoutingDataSource(dataSource1,dataSource2));
        // 设置mapper.xml的位置路径
        Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*/*.xml");
        factoryBean.setMapperLocations(resources);
        return factoryBean.getObject();
    }

    @Bean
    public PlatformTransactionManager transactionManager (@Qualifier("myRoutingDataSource")MyRoutingDataSource myRoutingDataSource){
        return new DataSourceTransactionManager(myRoutingDataSource);
    }
}

任务类实现

不同于以前,在spring-boot-starter-quartz中通过继承QuartzJobBean,覆写executeInternal方法,来实现任务类

@Slf4j
public class DemoJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        //这里边写你的任务所要干的任何事情
        JobDetail jobDetail = jobExecutionContext.getJobDetail();
        log.info("------任务名:" + jobDetail.getKey().getName() + ",组名:" +
                jobDetail.getKey().getGroup() + "------我是要执行的定时任务工作内容!");
    }

任务“增删改查”

任务详情对象

@Data
public class JobInfo implements Serializable {
    private static final long serialVersionUID = 8026140551673459050L;
    private int id;
    private String jobname;
    private String jobgroup;
    private String jobclassname;
    private String triggername;
    private String triggergroup;
    private String cronexpression;
    private String description;
    private String prefiretime;
    private String nextfiretime;
    private String state;
}

业务操作service类

public interface IJobService {
    //查询所有任务
    public List<JobInfo> getAllJobs();
    //恢复任务
    public boolean resumeJob(String jobName,String jobGroup);
    //停止任务
    public boolean pauseJob(String jobName,String jobGroup);
    //修改任务执行周期表达式
    public boolean reScheduleJob(String jobName,String jobGroup,String cronExpression);
    //删除任务
    public boolean deleteJob(String jobName,String jobGroup);
    //新增任务
    public int addJob(JobInfo jobInfo);
    //判断任务是否存在
    public int isJobExist(JobKey jobKey);

}
@Service
@Slf4j
public class JobServiceImp implements IJobService {
	//Springboot已经为我们自动装配了任务调度器Scheduler,
	//无需额外配置便可以注入使用,由Springboot为我们管理调度器
    @Autowired
    private Scheduler scheduler;

    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    public int isJobExist(JobKey jobKey) {
        int result = 1;
        try {
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
            if (jobDetail != null && triggers.size() > 0)
                result = 1;
            else if (jobDetail != null && triggers.size() == 0)
                result = 0;
            else
                result = -1;
        } catch (SchedulerException e) {
            result = -1;
            log.info("任务不存在!");
        }
        return result;
    }

    @Override
    public List<JobInfo> getAllJobs() {
        List<JobInfo> jobInfos = new ArrayList<>();
        try {
            List<String> groups = scheduler.getJobGroupNames();
            int i = 0;
            for(String group :groups) {
                GroupMatcher<JobKey> groupMatcher = GroupMatcher.groupEquals(group);
                Set<JobKey> jobKeys = scheduler.getJobKeys(groupMatcher);
                for(JobKey jobKey:jobKeys) {
                    JobInfo jobInfo = new JobInfo();
                    JobDetail jobDetail = scheduler.getJobDetail(jobKey);
                    jobInfo.setJobname(jobKey.getName());
                    jobInfo.setJobgroup(jobKey.getGroup());
                    jobInfo.setJobclassname(jobDetail.getJobClass().getName());
                    Trigger jobTrigger = scheduler.getTrigger(TriggerKey.triggerKey(jobKey.getName(),jobKey.getGroup()));
                    if (jobTrigger != null) {
                        Trigger.TriggerState tState = scheduler.getTriggerState(TriggerKey.triggerKey(jobKey.getName(),jobKey.getGroup()));
                        jobInfo.setTriggername(jobKey.getName());
                        jobInfo.setTriggergroup(jobKey.getGroup());
                        try {
                            CronTrigger cronTrigger = (CronTrigger)jobTrigger;
                            jobInfo.setCronexpression(cronTrigger.getCronExpression());
                        }catch (Exception e) {
                            log.info("不是CronTrigger");
                        }
                        if (jobTrigger.getNextFireTime() != null)
                            jobInfo.setNextfiretime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(jobTrigger.getNextFireTime()));
                        jobInfo.setDescription(jobDetail.getDescription());
                        jobInfo.setState(tState.name());
                        jobInfo.setId(i);
                        jobInfos.add(jobInfo);
                        i += 1;
                    } else {
                        jobInfo.setState("OVER");
                        jobInfo.setId(i);
                        jobInfos.add(jobInfo);
                        i += 1;
                    }
                }
            }
        } catch (SchedulerException e) {
            log.error(e.getMessage());
        }
        return jobInfos;
    }

    @Override
    public boolean resumeJob(String jobName, String jobGroup) {
        boolean result = true;
        try {
            scheduler.resumeJob(JobKey.jobKey(jobName,jobGroup));
        } catch (SchedulerException e) {
            result = false;
            log.error(e.getMessage());
        }
        return result;
    }

    @Override
    public boolean pauseJob(String jobName, String jobGroup) {
        boolean result = true;
        try {
            scheduler.pauseJob(JobKey.jobKey(jobName,jobGroup));
        } catch (SchedulerException e) {
            result = false;
            log.error(e.getMessage());
        }
        return result;
    }

    @Override
    public boolean reScheduleJob(String jobName, String jobGroup, String cronExpression) {
        //判断当前状态
        boolean result = true;
        try {
            Trigger.TriggerState triggerState = scheduler.getTriggerState(TriggerKey.triggerKey(jobName,jobGroup));
            CronTrigger cronTriggerOld = (CronTrigger)scheduler.getTrigger(TriggerKey.triggerKey(jobName,jobGroup));
            if (!cronTriggerOld.getCronExpression().equals(cronExpression)){
                CronTrigger cronTriggerNew = TriggerBuilder.newTrigger().withIdentity(jobName,jobGroup)
                        .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
                        .build();
                scheduler.rescheduleJob(TriggerKey.triggerKey(jobName,jobGroup),cronTriggerNew);
                if (triggerState.name().equals("PAUSED"))
                    this.pauseJob(jobName,jobGroup);
            }

        } catch (SchedulerException e) {
            result = false;
            log.error(e.getMessage());
        }
        return result;
    }

    @Override
    public boolean deleteJob(String jobName, String jobGroup) {
        boolean result = true;
        try {
            List<? extends Trigger> triggers = scheduler.getTriggersOfJob(JobKey.jobKey(jobName,jobGroup));
            if (triggers.size() > 0) {
                if (!"PAUSED".equals(scheduler.getTriggerState(TriggerKey.triggerKey(jobName,jobGroup)).name()))
                    scheduler.pauseTrigger(TriggerKey.triggerKey(jobName,jobGroup));
                scheduler.unscheduleJob(TriggerKey.triggerKey(jobName,jobGroup));
            }
            scheduler.deleteJob(JobKey.jobKey(jobName,jobGroup));
        } catch (SchedulerException e) {
            result = false;
            log.error(e.getMessage());
        }
        return result;
    }
    @Override
    public int addJob(JobInfo jobInfo) {
        int result = 0;
        int isJobExist = this.isJobExist(JobKey.jobKey(jobInfo.getJobname(),jobInfo.getJobgroup()));
        if (isJobExist == 1) {
            result = -1;
            log.info("任务已经存在!");
        } else {
            try {
                JobDetail jobDetail = null;
                if (isJobExist == 0) {
                    jobDetail = scheduler.getJobDetail(JobKey.jobKey(jobInfo.getJobname(),jobInfo.getJobgroup()));
                }else if (isJobExist == -1) {
                    jobDetail = JobBuilder.newJob(
                            (Class<? extends QuartzJobBean>)Class.forName(jobInfo.getJobclassname()))
                            .withIdentity(jobInfo.getJobname(),jobInfo.getJobgroup())
                            .withDescription(jobInfo.getDescription())
                            .storeDurably().build();
                }
                //如果jobInfo的cron表达式为空,则创建常规任务,反之创建周期任务
                if (!StringUtils.isEmpty(jobInfo.getCronexpression())) {
                    CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                            .withIdentity(jobInfo.getTriggername(),jobInfo.getTriggergroup())
                            .withSchedule(CronScheduleBuilder.cronSchedule(jobInfo.getCronexpression()))
                            .build();
                    scheduler.scheduleJob(jobDetail,cronTrigger);
                } else {
                    Trigger trigger = TriggerBuilder.newTrigger()
                            .withIdentity(jobInfo.getJobname(),jobInfo.getJobgroup())
                            .startAt(sdf.parse(jobInfo.getNextfiretime()))
                            .withSchedule(SimpleScheduleBuilder.simpleSchedule().
                                    withRepeatCount(0))
                            .build();
                    scheduler.scheduleJob(jobDetail,trigger);
                }

            }catch (ClassNotFoundException e) {
                result = 1;
                log.error("任务对应的Class类不存在");
            } catch (SchedulerException e) {
                result = 2;
                log.error("任务调度失败");
            } catch (ParseException e) {
                result = 3;
                log.error("时间转换出错");
            }
        }
        return result;
    }
}

前端接口controller类

@Controller
@RequestMapping(value = "/task")
@Slf4j
public class JobController {

    @Autowired
    private IJobService jobService;

    @GetMapping(value = "/ok")
    @ResponseBody
    public String sendok(){
        return "ok";
    }

    @PostMapping(value = "/add")
    @ResponseBody
    public Response addJob(@RequestBody JobInfo jobInfo) {
        Response response = null;
        switch (jobService.addJob(jobInfo)) {
            case -1 : response = new Response(500,"任务已存在!",false);break;
            case 0 : response = new Response(200,"success",true);break;
            case 1 : response = new Response(500,"没有该任务对应的 Java Class 类!",false);break;
            case 2 : response = new Response(500,"添加任务失败!",false);break;
            case 3 : response = new Response(500,"时间格式错误!",false);break;
        }
        return response;
    }

    @GetMapping(value = "/jobs")
    @ResponseBody
    public Response getAllJobs() {
        List<JobInfo> jobInfos = jobService.getAllJobs();
        return jobInfos.size() > 0 ? new Response(200,"success",jobInfos)
                : new Response(500,"No job",false);
    }

    @PostMapping(value = "/pause")
    @ResponseBody
    public Response pauseJob(String name, String group) {
        return jobService.pauseJob(name,group) ? new Response(200,"success",true)
                : new Response(500,"errror",false);
    }

    @PostMapping(value = "/resume")
    @ResponseBody
    public Response resumeJob(String name, String group) {
        return jobService.resumeJob(name,group) ? new Response(200,"success",true)
                : new Response(500,"errror",false);
    }

    @PostMapping(value = "/reschedule")
    @ResponseBody
    public Response reScheduleJob(String name, String group, String cron) {
        return jobService.reScheduleJob(name, group, cron) ? new Response(200,"success",true)
                : new Response(500,"errror",false);
    }

    @PostMapping(value = "/delete")
    @ResponseBody
    public Response deleteJob(String name, String group) {
        return jobService.deleteJob(name,group) ? new Response(200,"success",true)
                : new Response(500,"errror",false);
    }

    //校验是否是合法cron表达式
    @PostMapping(value = "/cron-check")
    @ResponseBody
    public Response checkCron(String cron) {
        boolean valide = false;
        try {
            valide = CronExpression.isValidExpression(cron);
        }catch (Exception e){
            log.error(e.getMessage());
        }
        return valide ? new Response(200,"success",true)
                : new Response(500,"cron表达式格式错误!",false);
    }
}

测试

Alt
在这里插入图片描述
可以看到新增的任务已经开始执行了。新增方法如果有参数nextfiretime没有cronexpression,那么任务只会在nextfiretime的时候执行一次。
其它接口已在公司项目测试通过,如果还有疑问,请留言或私信~

  • 7
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
Spring Boot应用中使用Quartz进行作业调度,可以通过配置Quartz持久化数据源来实现将Quartz作业信息存储到数据库中。如果使用了Dynamic多数据源,需要为Quartz指定使用哪个数据源。 具体步骤如下: 1. 配置Quartz持久化数据源,例如: ``` org.quartz.dataSource.quartzDataSource.driverClassName=com.mysql.jdbc.Driver org.quartz.dataSource.quartzDataSource.URL=jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&zeroDateTimeBehavior=convertToNull org.quartz.dataSource.quartzDataSource.username=root org.quartz.dataSource.quartzDataSource.password=root org.quartz.dataSource.quartzDataSource.maxConnections=10 org.quartz.dataSource.quartzDataSource.provider=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate ``` 2. 在Spring Boot应用中,创建一个实现了`org.springframework.boot.autoconfigure.quartz.QuartzDataSource`接口的类,用于动态获取Quartz持久化数据源的配置信息。例如: ``` @Configuration public class QuartzConfiguration implements QuartzDataSource { private final DynamicDataSource dynamicDataSource; public QuartzConfiguration(DynamicDataSource dynamicDataSource) { this.dynamicDataSource = dynamicDataSource; } @Override public DataSource getDataSource() { return dynamicDataSource.getDataSource("quartzDataSource"); } } ``` 在上述代码中,`DynamicDataSource`是自定义的动态数据源类,`getDataSource()`方法根据数据源名称获取对应的数据源。 3. 在Quartz的配置类中,使用上述`QuartzDataSource`实现类来获取持久化数据源。例如: ``` @Configuration public class QuartzConfig { @Autowired private QuartzDataSource quartzDataSource; @Bean public SchedulerFactoryBean schedulerFactoryBean() { SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setDataSource(quartzDataSource.getDataSource()); // 其他配置... return factory; } } ``` 在上述代码中,`schedulerFactoryBean()`方法创建了一个`SchedulerFactoryBean`实例,并将持久化数据源设置为`quartzDataSource`。这样就可以实现在Spring Boot应用中使用Dynamic多数据源的同,为Quartz配置持久化数据源了。
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值