Quartz是一款功能強大的任務調度器,能夠實現較爲複雜的調度功能,如每個月一號執行、天天凌晨執行、每週五執行等等,還支持分佈式調度。本文使用Springboot+Mybatis+Quartz實現對定時任務的增、刪、改、查、啓用、停用等功能。並把定時任務持久化到數據庫以及支持集羣。對於如何建立Springboot項目和與Mybatis整合能夠參考上篇文章:[spring-boot整合Mybatis如何部署在weblogic上
][1]。java
<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.propertiesweb
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.properties配置的詳細解釋能夠查看quartz官網。另外新建一張表TB_APP_QUARTZ用於存放定時任務基本信息和描述等信息,定時任務的增、刪、改、執行等功能與此表沒有任何關係。
quartz的11張表:
spring
//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數據源配置sql
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; } }
3.Quartz配置express
/** * 建立job 實例工廠,解決spring注入問題,若是使用默認會致使spring的@Autowired 沒法注入問題 * @author LLQ * */ @Component public class JobFactory extends AdaptableJobFactory{ @Autowired private AutowireCapableBeanFactory capableBeanFactory; @Override protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { //調用父類的方法 Object jobInstance = super.createJobInstance(bundle); //進行注入 capableBeanFactory.autowireBean(jobInstance); return jobInstance; } }
@Configuration public class SchedulerConfig implements ApplicationListener<ContextRefreshedEvent>{ @Autowired private JobFactory jobFactory; @Autowired @Qualifier("dataSource") private DataSource primaryDataSource; @Override public void onApplicationEvent(ContextRefreshedEvent event) { System.out.println("任務已經啓動..."+event.getSource()); } @Bean public SchedulerFactoryBean schedulerFactoryBean() throws IOException { //獲取配置屬性 PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties")); //在quartz.properties中的屬性被讀取並注入後再初始化對象 propertiesFactoryBean.afterPropertiesSet(); //建立SchedulerFactoryBean SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setQuartzProperties(propertiesFactoryBean.getObject()); //使用數據源,自定義數據源 factory.setDataSource(this.primaryDataSource); factory.setJobFactory(jobFactory); factory.setWaitForJobsToCompleteOnShutdown(true);//這樣當spring關閉時,會等待全部已經啓動的quartz job結束後spring才能徹底shutdown。 factory.setOverwriteExistingJobs(false); factory.setStartupDelay(1); return factory; } /* * 經過SchedulerFactoryBean獲取Scheduler的實例 */ @Bean(name="scheduler") public Scheduler scheduler() throws IOException { return schedulerFactoryBean().getScheduler(); } @Bean public QuartzInitializerListener executorListener() { return new QuartzInitializerListener(); } }
4.建立定時任務服務tomcat
@Service public class JobUtil { @Autowired @Qualifier("scheduler") private Scheduler scheduler; /** * 新建一個任務 * */ public String addJob(AppQuartz appQuartz) throws Exception { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date=df.parse(appQuartz.getStartTime()); if (!CronExpression.isValidExpression(appQuartz.getCronExpression())) { return "Illegal cron expression"; //表達式格式不正確 } JobDetail jobDetail=null; //構建job信息 if("JobOne".equals(appQuartz.getJobGroup())) { jobDetail = JobBuilder.newJob(JobOne.class).withIdentity(appQuartz.getJobName(), appQuartz.getJobGroup()).build(); } if("JobTwo".equals(appQuartz.getJobGroup())) { jobDetail = JobBuilder.newJob(JobTwo.class).withIdentity(appQuartz.getJobName(), appQuartz.getJobGroup()).build(); } //表達式調度構建器(即任務執行的時間,不當即執行) CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(appQuartz.getCronExpression()).withMisfireHandlingInstructionDoNothing(); //按新的cronExpression表達式構建一個新的trigger CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(appQuartz.getJobName(), appQuartz.getJobGroup()).startAt(date) .withSchedule(scheduleBuilder).build(); //傳遞參數 if(appQuartz.getInvokeParam()!=null && !"".equals(appQuartz.getInvokeParam())) { trigger.getJobDataMap().put("invokeParam",appQuartz.getInvokeParam()); } scheduler.scheduleJob(jobDetail, trigger); // pauseJob(appQuartz.getJobName(),appQuartz.getJobGroup()); return "success"; } /** * 獲取Job狀態 * @param jobName * @param jobGroup * @return * @throws SchedulerException */ public String getJobState(String jobName, String jobGroup) throws SchedulerException { TriggerKey triggerKey = new TriggerKey(jobName, jobGroup); return scheduler.getTriggerState(triggerKey).name(); } //暫停全部任務 public void pauseAllJob() throws SchedulerException { scheduler.pauseAll(); } //暫停任務 public String pauseJob(String jobName, String jobGroup) throws SchedulerException { JobKey jobKey = new JobKey(jobName, jobGroup); JobDetail jobDetail = scheduler.getJobDetail(jobKey); if (jobDetail == null) { return "fail"; }else { scheduler.pauseJob(jobKey); return "success"; } } //恢復全部任務 public void resumeAllJob() throws SchedulerException { scheduler.resumeAll(); } // 恢復某個任務 public String resumeJob(String jobName, String jobGroup) throws SchedulerException { JobKey jobKey = new JobKey(jobName, jobGroup); JobDetail jobDetail = scheduler.getJobDetail(jobKey); if (jobDetail == null) { return "fail"; }else { scheduler.resumeJob(jobKey); return "success"; } } //刪除某個任務 public String deleteJob(AppQuartz appQuartz) throws SchedulerException { JobKey jobKey = new JobKey(appQuartz.getJobName(), appQuartz.getJobGroup()); JobDetail jobDetail = scheduler.getJobDetail(jobKey); if (jobDetail == null ) { return "jobDetail is null"; }else if(!scheduler.checkExists(jobKey)) { return "jobKey is not exists"; }else { scheduler.deleteJob(jobKey); return "success"; } } //修改任務 public String modifyJob(AppQuartz appQuartz) throws SchedulerException { if (!CronExpression.isValidExpression(appQuartz.getCronExpression())) { return "Illegal cron expression"; } TriggerKey triggerKey = TriggerKey.triggerKey(appQuartz.getJobName(),appQuartz.getJobGroup()); JobKey jobKey = new JobKey(appQuartz.getJobName(),appQuartz.getJobGroup()); if (scheduler.checkExists(jobKey) && scheduler.checkExists(triggerKey)) { CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); //表達式調度構建器,不當即執行 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(appQuartz.getCronExpression()).withMisfireHandlingInstructionDoNothing(); //按新的cronExpression表達式從新構建trigger trigger = trigger.getTriggerBuilder().withIdentity(triggerKey) .withSchedule(scheduleBuilder).build(); //修改參數 if(!trigger.getJobDataMap().get("invokeParam").equals(appQuartz.getInvokeParam())) { trigger.getJobDataMap().put("invokeParam",appQuartz.getInvokeParam()); } //按新的trigger從新設置job執行 scheduler.rescheduleJob(triggerKey, trigger); return "success"; }else { return "job or trigger not exists"; } } }
@PersistJobDataAfterExecution @DisallowConcurrentExecution @Component public class JonOne implements Job{ @Override public void execute(JobExecutionContext context) throws JobExecutionException{ JobDataMap data=context.getTrigger().getJobDataMap(); String invokeParam =(String) data.get("invokeParam"); //在這裏實現業務邏輯 } }
@PersistJobDataAfterExecution @DisallowConcurrentExecution @Component public class JobTwo implements Job{ @Override public void execute(JobExecutionContext context) throws JobExecutionException{ JobDataMap data=context.getTrigger().getJobDataMap(); String invokeParam =(String) data.get("invokeParam"); //在這裏實現業務邏輯 } }
說明:每一個定時任務都必須有一個分組,名稱和corn表達式,corn表達式也就是定時任務的觸發時間,關於corn表達式格式以及含義能夠參考一些網絡資源。每一個定時任務都有一個入口類在這裏我把類名當成定時任務的分組名稱,例如:只要建立定時任務的分組是JobOne的都會執行JobOne這個任務類裏面的邏輯。若是定時任務須要額外的參數可使用JobDataMap傳遞參數,固然也能夠從數據庫中獲取須要的數據。@PersistJobDataAfterExecution和@DisallowConcurrentExecution註解是不讓某個定時任務併發執行,只有等當前任務完成下一個任務纔會去執行。網絡
5.封裝定時任務接口mybatis
@RestController public class JobController { @Autowired private JobUtil jobUtil; @Autowired private AppQuartzService appQuartzService; //添加一個job @RequestMapping(value="/addJob",method=RequestMethod.POST) public ReturnMsg addjob(@RequestBody AppQuartz appQuartz) throws Exception { appQuartzService.insertAppQuartzSer(appQuartz); result=jobUtil.addJob(appQuartz); } //暫停job @RequestMapping(value="/pauseJob",method=RequestMethod.POST) public ReturnMsg pausejob(@RequestBody Integer[]quartzIds) throws Exception { AppQuartz appQuartz=null; if(quartzIds.length>0){ for(Integer quartzId:quartzIds) { appQuartz=appQuartzService.selectAppQuartzByIdSer(quartzId).get(0); jobUtil.pauseJob(appQuartz.getJobName(), appQuartz.getJobGroup()); } return new ReturnMsg("200","success pauseJob"); }else { return new ReturnMsg("404","fail pauseJob"); } } //恢復job @RequestMapping(value="/resumeJob",method=RequestMethod.POST) public ReturnMsg resumejob(@RequestBody Integer[]quartzIds) throws Exception { AppQuartz appQuartz=null; if(quartzIds.length>0) { for(Integer quartzId:quartzIds) { appQuartz=appQuartzService.selectAppQuartzByIdSer(quartzId).get(0); jobUtil.resumeJob(appQuartz.getJobName(), appQuartz.getJobGroup()); } return new ReturnMsg("200","success resumeJob"); }else { return new ReturnMsg("404","fail resumeJob"); } } //刪除job @RequestMapping(value="/deletJob",method=RequestMethod.POST) public ReturnMsg deletjob(@RequestBody Integer[]quartzIds) throws Exception { AppQuartz appQuartz=null; for(Integer quartzId:quartzIds) { appQuartz=appQuartzService.selectAppQuartzByIdSer(quartzId).get(0); String ret=jobUtil.deleteJob(appQuartz); if("success".equals(ret)) { appQuartzService.deleteAppQuartzByIdSer(quartzId); } } return new ReturnMsg("200","success deleteJob"); } //修改 @RequestMapping(value="/updateJob",method=RequestMethod.POST) public ReturnMsg modifyJob(@RequestBody AppQuartz appQuartz) throws Exception { String ret= jobUtil.modifyJob(appQuartz); if("success".equals(ret)) { appQuartzService.updateAppQuartzSer(appQuartz); return new ReturnMsg("200","success updateJob",ret); }else { return new ReturnMsg("404","fail updateJob",ret); } } //暫停全部 @RequestMapping(value="/pauseAll",method=RequestMethod.GET) public ReturnMsg pauseAllJob() throws Exception { jobUtil.pauseAllJob(); return new ReturnMsg("200","success pauseAll"); } //恢復全部 @RequestMapping(value="/repauseAll",method=RequestMethod.GET) public ReturnMsg repauseAllJob() throws Exception { jobUtil.resumeAllJob(); return new ReturnMsg("200","success repauseAll"); } }