Quartz学习之Spring整合Quartz(JobDetailBean方式)

背景

最近在做项目,有个需求:就是需要定时去执行某一项任务。谈到定时,我第一个想到就是QuartzJob 这个任务调度系统,借此博客,给大家分享一下使用过程中的一些技巧。

简介

官网的大概意思:
quartz是一个功能丰富的开源的任务调用系统,它可以创建简单或者复杂的几十、几百、甚至成千上万的job。此外,quartz调度器还支持JTA事务和集群。(原谅我这蹩脚的翻译)

Quartz是一款功能强大的任务调度器,可以实现较为复杂的调度功能,如每月一号执行、每天凌晨执行、每周五执行等等,还支持分布式调度。本文使用Springboot+Mybatis+Quartz实现对定时任务的增、删、改、查、启用、停用等功能。并把定时任务持久化到数据库以及支持集群。对于如何创建Springboot项目和与Mybatis整合可以参考上篇文章:[spring-boot整合Mybatis如何部署在weblogic上
][1]。

Quartz的3个基本要素

  1. Scheduler:任务调度器。所有的调度都是由它控制,所有的任务都是从这里开始。
  2. Trigger: 触发器。决定什么时候来执行任务。
  3. JobDetail & Job: JobDetail定义的是任务数据,而真正的执行逻辑是在Job中。使用JobDetail + Job而不是Job,这是因为任务是有可能并发执行,如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。而JobDetail & Job 方式,sheduler每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以规避并发访问的问题。

Spring创建JobDetail的方式

定时任务两种方式,Spring很好的封装使用Quartz的细节,是利用SPring封装的Quartz类进行特定方法的实现(这个说法可能不对)

利用JobDetailBean包装QuartzJobBean子类(即Job类)的实例。

采用第一种方法 创建job类,一定要继承QuartzJobBean ,实现 executeInternal(JobExecutionContext
jobexecutioncontext)方法,此方法就是被调度任务的执行体,然后将此Job类的实例直接配置到JobDetailBean中即可。这种方法和在普通的Quartz编程中是一样的。

Scheduler接口

Scheduler翻译成调度器,Quartz通过调度器来注册、暂停、删除Trigger和JobDetail。Scheduler还拥有一个SchedulerContext,顾名思义就是上下文,通过SchedulerContext我们可以获取到触发器和任务的一些信息。

JobDetail接口

Job接口是真正需要执行的任务。JobDetail接口相当于将Job接口包装了一下,Trigger和Scheduler实际用到的都是JobDetail。

Trigger

Trigger可以翻译成触发器,通过cron表达式或是SimpleScheduleBuilder等类,指定任务执行的周期。系统时间走到触发器指定的时间的时候,触发器就会触发任务的执行。

这里我只给大家介绍一些常用的方法,其余的可以自己查看文档:

  • withIdentity() 给触发器一些属性 比如名字,组名。
  • startNow() 立刻启动
  • withSchedule(ScheduleBuilder schedBuilder) 以某种触发器触发。
  • usingJobData(String dataKey, Boolean value) 给具体job传递参数。

举个创建Trigger的例子:

Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1)
                .repeatForever()).build();

CronScheduleBuilder(触发器)

Trigger有好几种触发器,他们都自带方法,这个是最常用的,使用corn表达式,可以代替其他所有的触发器,所以那几个触发器就不介绍了

// 主要就是写corn表达式
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1")
                //加入 scheduler之后立刻执行
                .startNow()
                //定时 ,每个1秒钟执行一次
                .withSchedule(cronSchedule("0 0/2 8-17 * * ?") // 每天8:00-17:00,每隔2分钟执行一次
                        ).build();

JobDetail & Job

jobdetail 就是对job的定义,而job是具体执行的逻辑内容。
具体的执行的逻辑需要实现 job类,并实现execute方法。
这里为什么需要有个JobDetai来作为job的定义,为什么不直接使用job?
解释:如果使用jobdetail来定义,那么每次调度都会创建一个new job实例,这样带来的好处就是任务并发执行的时候,互不干扰,不会对临界资源造成影响。

项目中出现的问题

如何禁止并发执行?
答:项目中出现了一种情况,本来job执行时间只需要10s,但是由于数据库量增大之后,执行时间变成了60s,而我设置的间隔时间是30s,这样就会出现上次任务还没执行完成,下次任务就开始执行了。所以,在这种情况下,我们要禁止quart的并发操作。

2种方式:

spring中将job的concurrent属性设置为false。默认是true 如下:

<bean id="scheduleJob" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="scheduleBusinessObject"/>
    <property name="targetMethod" value="doIt"/>
    <property name="concurrent" value="false"/>
</bean>

job类上加上注解@DisallowConcurrentExecution。

@DisallowConcurrentExecution
public class HelloQuartz implements Job {
     

    @Override
    public void execute(JobExecutionContext jobExecutionContext) {
     
        JobDetail detail = jobExecutionContext.getJobDetail();
        String name = detail.getJobDataMap().getString("name");
        System.out.println("my job name is  " + name + " at " + new Date());

    }
}

注意:@DisallowConcurrentExecution是对JobDetail实例生效,如果一个job类被不同的jobdetail引用,这样是可以并发执行。

如何使用Quartz

1. 添加依赖

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

2. 创建配置文件

在maven项目的resource目录下创建quartz.properties

org.quartz.scheduler.instanceName = MyScheduler
org.quartz.scheduler.instanceId = AUTO
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false

#线程池配置
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

#持久化配置
org.quartz.jobStore.misfireThreshold = 50000
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#支持集群
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.useProperties:true
org.quartz.jobStore.clusterCheckinInterval = 15000
#使用weblogic连接Oracle驱动
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.weblogic.WebLogicOracleDelegate
#org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.dataSource = qzDS
#数据源连接信息,quartz默认使用c3p0数据源可以被自定义数据源覆盖
org.quartz.dataSource.qzDS.driver = oracle.jdbc.driver.OracleDriver
org.quartz.dataSource.qzDS.URL = jdbc:oracle:thin:@localhost:1521/XE
org.quartz.dataSource.qzDS.user = root
org.quartz.dataSource.qzDS.password = 123456
org.quartz.dataSource.qzDS.maxConnections = 10

说明:在使用quartz做持久化的时候需要用到quartz的11张表,可以去quartz官网下载对应版本的quartz,解压打开docs/dbTables里面有对应数据库的建表语句(也可以在数据库中执行如下来自Quartz官方的脚本。)。关于quartz.properties配置的详细解释可以查看quartz官网。另外新建一张表TB_APP_QUARTZ用于存放定时任务基本信息和描述等信息,定时任务的增、删、改、执行等功能与此表没有任何关系。

//TB_APP_QUARTZ表的实体类
public class AppQuartz {
     
    private Integer quartzId;  //id  主键
    private String jobName;  //任务名称
    private String jobGroup;  //任务分组
    private String startTime;  //任务开始时间
    private String cronExpression;  //corn表达式
    private String invokeParam;//需要传递的参数
    ...省略set get
}

Durid数据源配置

application.properties配置文件

server.port=8889
logging.level.=INFO
server.tomcat.uri-encoding=UTF-8
server.connection-timeout=5000
spring.resources.static-locations=classpath:static/,file:static/

mybatis.configuration.mapUnderscoreToCamelCase=true
mybatis.type-aliases-package=com.qz.bean
#-----------------------durid------------------------------------
spring.dataSource.primaryDataSource.type=com.alibaba.druid.pool.DruidDataSource
spring.dataSource.primaryDataSource.url=jdbc:oracle:thin:@10.2.14.238:1521/testdb
spring.dataSource.primaryDataSource.username=dev_monitor
spring.dataSource.primaryDataSource.password=welcome1
spring.dataSource.primaryDataSource.driverClassName = oracle.jdbc.driver.OracleDriver
spring.dataSource.primaryDataSource.initialSize = 5
spring.dataSource.primaryDataSource.minIdle = 5
spring.dataSource.primaryDataSource.maxActive = 15
spring.dataSource.primaryDataSource.maxWait = 60000
spring.dataSource.primaryDataSource.timeBetweenEvictionRunsMillis = 60000
spring.dataSource.primaryDataSource.minEvictableIdleTimeMillis = 300000
spring.dataSource.primaryDataSource.validationQuery = SELECT 1 FROM DUAL
spring.dataSource.primaryDataSource.testWhileIdle = true
spring.dataSource.primaryDataSource.testOnBorrow = true
spring.dataSource.primaryDataSource.testOnReturn = true
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "spring.dataSource.primaryDataSource")
public class DuridBean {
     
         private String type;

        private String url;

        private String username;

        private String password;

        private String driverClassName;

        private Integer initialSize;

        private Integer minIdle;

        private Integer maxActive;

        private Integer maxWait;

        private Integer timeBetweenEvictionRunsMillis;

        private Integer minEvictableIdleTimeMillis;

        private String validationQuery;

        private Boolean testWhileIdle;

        private Boolean testOnBorrow;

        private Boolean testOnReturn;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import com.alibaba.druid.pool.DruidDataSource;

@Configuration
public class DuridDatasource {
     
     @Autowired
     private DuridBean druidPrimaryDataSourceConfigProperties;

    @Bean(name="dataSource")
    @Primary
    public DataSource primaryDataSource (){
     
        DruidDataSource datasource = new DruidDataSource();

        datasource.setUrl(this.druidPrimaryDataSourceConfigProperties.getUrl());
        datasource.setUsername(this.druidPrimaryDataSourceConfigProperties.getUsername());
        datasource.setPassword(this.druidPrimaryDataSourceConfigProperties.getPassword());
        datasource.setDriverClassName(this.druidPrimaryDataSourceConfigProperties.getDriverClassName());


        datasource.setInitialSize(this.druidPrimaryDataSourceConfigProperties.getInitialSize());
        datasource.setMinIdle(this.druidPrimaryDataSourceConfigProperties.getMinIdle());
        datasource.setMaxActive(this.druidPrimaryDataSourceConfigProperties.getMaxActive());
        datasource.setMaxWait(this.druidPrimaryDataSourceConfigProperties.getMaxWait());
        datasource.setTimeBetweenEvictionRunsMillis(this.druidPrimaryDataSourceConfigProperties.getTimeBetweenEvictionRunsMillis());
        datasource.setMinEvictableIdleTimeMillis(this.druidPrimaryDataSourceConfigProperties.getMinEvictableIdleTimeMillis());
        datasource.setValidationQuery(this.druidPrimaryDataSourceConfigProperties.getValidationQuery());
        datasource.setTestWhileIdle(this.druidPrimaryDataSourceConfigProperties.getTestWhileIdle());
        datasource.setTestOnBorrow(this.druidPrimaryDataSourceConfigProperties.getTestOnBorrow());
        datasource.setTestOnReturn(this.druidPrimaryDataSourceConfigProperties.getTestOnReturn());

        return datasource;
    }

}

说明:每个定时任务都必须有一个分组,名称和corn表达式,corn表达式也就是定时任务的触发时间,关于corn表达式格式以及含义可以参考一些网络资源。每个定时任务都有一个入口类在这里我把类名当成定时任务的分组名称,例如:只要创建定时任务的分组是JobOne的都会执行JobOne这个任务类里面的逻辑。如果定时任务需要额外的参数可以使用JobDataMap传递参数,当然也可以从数据库中获取需要的数据。@PersistJobDataAfterExecution和@DisallowConcurrentExecution注解是不让某个定时任务并发执行,只有等当前任务完成下一个任务才会去执行。

部分知识引用自:
https://blog.csdn.net/yuanlaishini2010/article/details/8874991?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

https://blog.csdn.net/bicheng4769/article/details/81097305

https://www.cnblogs.com/FatShallot/p/12834352.html

你可能感兴趣的:(定时任务)