提醒:以下有代码示例,建议浏览器打开,或者手机横屏阅读,避免影响阅读体验。
以前介绍过quartz 的入门使用,当时使用的RAMJobStore 存储方式,这是基于内存 存储Job的相关信息,比如cronExpression 等信息。
这时,当我们重启软、硬件服务时,会导致内存中的数据丢失。也会导致正在执行的job,被强行中断,比如一个job需要执行15分钟,我在第10分钟的时候,重启服务了。那,在服务启动后,该job,已产生的数据,该如何处理?是等下次job再次执行,重新更新,还是立即重新执行??这些都不确定, 会带来一系列麻烦的问题。
今天要说的解决方案就是: 基于JDBCJobStore 实现Job 的动态CRUD。
这里是官方文档http://www.quartz-scheduler.org/documentation/quartz-2.1.x/tutorials/tutorial-lesson-09.html,
在JDBCJobStore 模块下介绍了其几个配置的关键点,可先了解一下,再看下文实现过程。
惯例,我们使用Spring boot,构建一个Srping mvc服务,
1. 加上quartz 的依赖
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.1</version>
</dependency>
2. 添加quartz.properties : 在 application.properties 平级目录下,新增 quartz.properties 文件,添加如下配置:
# quartz begin
# JobStoreCMT用来适配Spring 等事务管理。JobStoreTX 是quartz自己的事务管理, 但这样理解,好像也不对啊
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# 表前缀
org.quartz.jobStore.tablePrefix = QRTZ_
# 数据源配置
org.quartz.jobStore.dataSource = my_quartz_datasource
org.quartz.jobStore.useProperties = true
# 实例化ThreadPool时,使用的线程类为SimpleThreadPool
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
# threadCount和threadPriority将以setter的形式注入ThreadPool实例
# 并发个数
org.quartz.threadPool.threadCount = 5
# 优先级
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
org.quartz.jobStore.misfireThreshold = 5000
#==============================================================
#Configure DataSource
#==============================================================
org.quartz.dataSource.my_quartz_datasource.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.my_quartz_datasource.URL = jdbc:mysql://localhost:33306/s_web?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
org.quartz.dataSource.my_quartz_datasource.user = root
org.quartz.dataSource.my_quartz_datasource.password = _hx7h82gfc28c250n
org.quartz.dataSource.my_quartz_datasource.maxConnections = 30
# quartz end
3. 在mysql 数据库中,建立相关表单,http://www.quartz-scheduler.org/downloads/ 下载quartz的安装包,在docs/dbTables 中,找到相应的sql,我使用的是mysql,选择了tables_mysql_innodb.sql,注意,表的前缀,与properties中配置一致,在mysql 中执行sql 语句,建表。
4. spring boot 中,添加shedule相关bean 的配置:
自定义工厂类:
/**
* Created on 2018-07-15
* @author tangyuan
*/
@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;
}
新建ConfigBeans.java 进行相关bean 注入
@Configuration
public class ConfigBeans {
@Autowired
private MyJobFactory myJobFactory; //自定义的factory
//获取工厂bean
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
try {
schedulerFactoryBean.setQuartzProperties(quartzProperties());
schedulerFactoryBean.setJobFactory(myJobFactory);
} catch (IOException e) {
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();
}
//quartz初始化监听器,监听器可以监听到工程的启动,在工程停止再启动时可以让已有的
// 定时任务继续进行。 注意:还是依托于spring 容器。当spring mvc 重启时,quartz 正在执行的job依旧会被中断。
@Bean
public QuartzInitializerListener executorListener() {
return new QuartzInitializerListener();
}
//创建schedule
@Bean(name = "scheduler")
public Scheduler scheduler() {
return schedulerFactoryBean().getScheduler();
}
}
5. 构建一个job
/**
* Created on 2018-07-15
* @author tangyuan
*/
public class TestJob implements Job {
private static Logger logger = LoggerFactory.getLogger(HelloJob.class);
public TestJob() {
}
public void execute(JobExecutionContext context) {
logger.info("TestJob 执行时间: " + new Date());
}
}
6. 构建Controller,用于CRUD job, 这里只写了一个add,用于示例。如下 api,我们将 新增job 的类全路径,,及其他参数,传入。使用类全路径,实例化这个class对象。 quartz 在接收到这些参数后,会自动根据在quartz.properties 中配置的数据源,表信息,进行表的插入等操作,并开启执行job。随后,可在控制台,看到每隔10秒,job输出其执行时间。
http://localhost:8080/addjob?jobClassName=com.***.web.job.NewJob&jobName=NewJob&groupName=group1
/**
* Created on 2018-07-15
* @author tangyuan
*/
@Controller
public class QuartzJobController {
@ResponseBody
@RequestMapping(value="/addjob")
public void addjob(@RequestParam(value="jobClassName")String jobClassName,
@RequestParam(value = "jobName") String jobName,
@RequestParam(value = "groupName") String groupName
) throws Exception
{
// 通过SchedulerFactory获取一个调度器实例
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();
// 启动调度器
sched.start();
//使用类全路径,实例化这个class对象。
Class newClass = Class.forName(jobClassName);
JobDetail job = newJob(newClass).withIdentity(jobName, groupName).build();
Trigger trigger = newTrigger().withIdentity(jobName, groupName).startNow().withSchedule(simpleSchedule()
.withIntervalInSeconds(10)
.repeatForever()).build();
sched.scheduleJob(job, trigger);
}
}