spring中使用quartz框架(持久化到数据库+springboot)

Source

本例是在springboot中通过读取数据库的定时任务信息,动态生成quartz定时任务

1、导入依赖:

<dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.1</version>
        </dependency>
        <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-context-support</artifactId>
             <version>${spring.version}</version>
        </dependency>



2、在项目中添加quartz.properties文件(这样就不会走它自带的properties文件)

# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
#
 
#默认或是自己改名字都行
org.quartz.scheduler.instanceName: DefaultQuartzScheduler

#如果使用集群,instanceId必须唯一,设置成AUTO
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: 60000
#============================================================================
# Configure JobStore
#============================================================================
 
#
#org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore

#存储方式使用JobStoreTX,也就是数据库
org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#使用自己的配置文件
org.quartz.jobStore.useProperties:true
#数据库中quartz表的表名前缀
org.quartz.jobStore.tablePrefix:qrtz_
org.quartz.jobStore.dataSource:qzDS
#是否使用集群(如果项目只部署到 一台服务器,就不用了)
org.quartz.jobStore.isClustered = true
 
#============================================================================
# Configure Datasources
#============================================================================
#配置数据源
org.quartz.dataSource.qzDS.driver:com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL:jdbc:mysql://10.4.33.251:3306/ecif_orgin
org.quartz.dataSource.qzDS.user:reader1
org.quartz.dataSource.qzDS.password:Reader12341
org.quartz.dataSource.qzDS.maxConnection:10


3、在数据库中创建quartz相关的表

1)进入quartz的官网http://www.quartz-scheduler.org/,点击Downloads,下载后在目录\docs\dbTables下有常用数据库创建quartz表的脚本。

2)百度去搜创建quartz表


4、自定义MyJobFactory,解决spring不能在quartz中注入bean的问题

package com.nnfe.ecif.domain.bean;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;

@Component
public class MyJobFactory extends AdaptableJobFactory {
	 
	  @Autowired
	  private AutowireCapableBeanFactory capableBeanFactory;
	 
	  @Override
	  protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
	    Object jobInstance = super.createJobInstance(bundle);
	    capableBeanFactory.autowireBean(jobInstance); //这一步解决不能spring注入bean的问题
	    return jobInstance;
	  }

}

5、创建调度器schedule

package com.nnfe.ecif.config;

import java.io.IOException;
import java.util.Properties;

import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import com.nnfe.ecif.domain.bean.MyJobFactory;
  
@Configuration  
public class QuartzConfigration {
	
	 @Autowired
	 private MyJobFactory myJobFactory;  //自定义的factory
	 
	
//获取工厂bean 
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
      SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
      try {
		schedulerFactoryBean.setQuartzProperties(quartzProperties());
		schedulerFactoryBean.setJobFactory(myJobFactory);
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
      return schedulerFactoryBean;
    }
    
//指定quartz.properties
    @Bean
    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }

//创建schedule  
    @Bean(name = "scheduler")
    public Scheduler scheduler() {
      return schedulerFactoryBean().getScheduler();
    }
}

6、更新quartz中的任务

首先我们需要自己创建一张表,用来存放trigger的信息,然后从数据库读取这些信息来随时更新定时任务

现在我的数据库中有两个定时任务,注意:job_name存放的任务类的全路径,在quartz中通过jobName和jobGroup来确定trigger的唯一性,所以这两列为联合唯一索引。   

接着创建实体类:

import javax.persistence.Column;  
import javax.persistence.Entity;  
import javax.persistence.GeneratedValue;  
import javax.persistence.GenerationType;  
import javax.persistence.Id;  
import javax.persistence.Table;

import org.hibernate.annotations.GenericGenerator;
  
@Entity
@Table(name = "c_schedule_triggers")
public class CScheduleTrigger {  
    @Id  
    @GenericGenerator(name = "mysqlNative", strategy = "native")
    @GeneratedValue(generator = "mysqlNative") 
      private Integer id;  
  
      @Column  
      private String cron;  //时间表达式
      
      private String status; //使用状态 0:禁用   1:启用
      
      private String jobName; //任务名称
      
      private String jobGroup; //任务分组

更新quartz中的任务

package com.nnfe.ecif.domain.service.impl;

import java.util.List;  

import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.Job;  
import org.quartz.JobBuilder;  
import org.quartz.JobDetail;  
import org.quartz.JobKey;  
import org.quartz.Scheduler;  
 
import org.quartz.TriggerBuilder;  
import org.quartz.TriggerKey;  
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import com.nnfe.ecif.domain.orm.w.CScheduleTriggerRepository;
import com.nnfe.ecif.domain.orm.w.CScheduleTrigger;
import com.nnfe.ecif.domain.service.ScheduleTriggerService;

@Service 
public class ScheduleTriggerServiceImpl implements ScheduleTriggerService{  
	private static final Logger logger =  LoggerFactory.getLogger(ScheduleTriggerServiceImpl.class); 
    @Autowired
    private Scheduler scheduler;

    @Autowired
    private CScheduleTriggerRepository triggerRepository;

    @Override
    @Scheduled(cron="0 0 23:00 * * ?")  //每天晚上11点调用这个方法来更新quartz中的任务
    public void refreshTrigger() {  
        try {  
        	//查询出数据库中所有的定时任务
            List<CScheduleTrigger> jobList = triggerRepository.queryAll();
            if(jobList!=null){
             	for(CScheduleTrigger scheduleJob : jobList){
            		String status = scheduleJob.getStatus(); //该任务触发器目前的状态
            		TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
            		CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            		 //说明本条任务还没有添加到quartz中
                    if (null == trigger) {
                    	if(status.equals("0")){ //如果是禁用,则不用创建触发器
                    		continue;
                    	}
                    	
                        JobDetail jobDetail=null;
						try {
							//创建JobDetail(数据库中job_name存的任务全路径,这里就可以动态的把任务注入到JobDetail中)
							jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(scheduleJob.getJobName()))
							    .withIdentity(scheduleJob.getJobName(), scheduleJob.getJobGroup()).build();

	                        //表达式调度构建器
	                        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob
	                            .getCron());

	                        //按新的cronExpression表达式构建一个新的trigger
	                        trigger = TriggerBuilder.newTrigger().withIdentity(scheduleJob.getJobName(), scheduleJob.getJobGroup()).withSchedule(scheduleBuilder).build();

	                        //把trigger和jobDetail注入到调度器
	                        scheduler.scheduleJob(jobDetail, trigger);
						} catch (ClassNotFoundException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
                        
                    } else {  //说明查出来的这条任务,已经设置到quartz中了
                        // Trigger已存在,先判断是否需要删除,如果不需要,再判定是否时间有变化	
                    	if(status.equals("0")){ //如果是禁用,从quartz中删除这条任务
                    		JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
                    		scheduler.deleteJob(jobKey);
                    		continue;
                    	}
                    	String searchCron = scheduleJob.getCron(); //获取数据库的
                    	String currentCron = trigger.getCronExpression();
                    	if(!searchCron.equals(currentCron)){  //说明该任务有变化,需要更新quartz中的对应的记录
                    		//表达式调度构建器
                            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(searchCron);

                            //按新的cronExpression表达式重新构建trigger
                            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
                                .withSchedule(scheduleBuilder).build();

                            //按新的trigger重新设置job执行
                            scheduler.rescheduleJob(triggerKey, trigger);
                    	}
                    }
            	}
            }
        } catch (Exception e) {  
           logger.error("定时任务每日刷新触发器任务异常,在ScheduleTriggerServiceImpl的方法refreshTrigger中,异常信息:",e);
        }  
    }


7、自定义任务

@Component 
public class MyTask1 implements Job{
	//这里就可以通过spring注入bean了
	@Autowired
	private CScheduleTriggerRepository jobRepository;
	
	@Autowired
	private CScheduleRecordsRepository recordsRepository;

	@Override
	public void execute(JobExecutionContext context)
			throws JobExecutionException {
		boolean isExecute = false;  //是否已执行业务逻辑
		boolean flag = false;  //业务逻辑执行后返回结果
		try{
			//可以通过context拿到执行当前任务的quartz中的很多信息,如当前是哪个trigger在执行该任务
			CronTrigger trigger = (CronTrigger) context.getTrigger();
			String corn = trigger.getCronExpression();
			String jobName = trigger.getKey().getName();
			String jobGroup = trigger.getKey().getGroup();



要搞清楚一个问题:从数据库读取任务信息动态生成定时任务,和把quartz持久化到数据库是没有关系的。

前者是我们自己定义的业务表,而后者是quartz使用自己的表来存储信息。持久化到数据库后,就算服务器重启或是多个quartz节点也没关系,因为他们共享数据库中的任务信息。