一、前言
最近工作需求,需要整合一个quartz框架的管理界面,要求数据持久化,前端界面管理。方便管理所有的定时任务,同时进行任务状态的监控。
相关技术:
spring
springMvc
mybatis【这个看你们自己使用的数据源】
quartz
二、前端界面
前端界面各位请自行查找,不管是使用jquery还是vue。只要能实现请求发送就行。界面要实现基础的增删查改,暂停启动。
具体可以参考这篇文档,界面可以直接参照使用。
https://blog.csdn.net/u012907049/article/details/70273080
该博客介绍了一种持久化存储方式,不过由于没有采用spring装配bean的方式的方式。导致每次都要新建对象,而且无法指定调度器,所以兼容性不太适用。
三、持久化存储
首先quartz是提供了一套功能强大的持久化存储方案的。通过使用这个套框架,所有对job的增删改操作,都会记录到数据库中。
PS:查询按照需求自行写,查数据库
1:搭建基础的springMvc工程,确保正常启动,这里不做详细说明。
2:在搭建好的spring工程中加入下面两个quartz依赖。
这两个就是quartz框架需要的所有依赖
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.3.2</version>
</dependency>
3:配置bean
在spring的配置文件【就是你web.xml配置指向的文件,如applicationContext.xml】中声明一个SchedulerFactoryBean
如果你想要和其他区分开来,你把id和name都改成叫scheduler_test都行。
<!-- quartz持久化存储 -->
<bean id="scheduler" name="scheduler"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean" >
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:scan-quartz.properties" />
<property name="applicationContextSchedulerContextKey"
value="applicationContextKey" />
<property name="autoStartup" value="true" />
</bean>
这里解释下,首先
1:我们声明了一个scheduler ,这个就是我们之后将要操作的对象。所有对这个对象的操作都会反馈到数据库中
2:我们对这个对象 声明了一个数据库 <property name="dataSource" ref="dataSource" />。这个数据源自行配置。
比如我使用mybatis的数据库,id为dataSource。这个就是我提供给scheduler_test 的数据源。这里就是第一种数据源配置方式,也是最灵活的一种方式。
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<!-- <property name="driverClassName" value="com.mysql.jdbc.Driver" /> -->
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root" />
<property name="password" value="123456" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="0"></property>
<!-- 连接池最大数量 -->
<property name="maxActive" value="20"></property>
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="20"></property>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="1"></property>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="6000"></property>
</bean>
<!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath:com/example/demo/mapper/*.xml"></property>
</bean>
<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.demo.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
<!-- (事务管理)transaction manager, use JtaTransactionManager for global tx -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
3:我们配置 <property name="autoStartup" value="true" /> ,这个会让任务在程序启动时加载
4:我们配置具体的quarzt的任务参数,指明需要读取的文件 <property name="configLocation" value="classpath:scan-quartz.properties" />
4:配置scan-quartz.properties
在bean的声明里我们读取了一个配置文件
<property name="configLocation" value="classpath:scan-quartz.properties" />
我们在项目的资源文件路径下放了一个 scan-quartz.properties。这个就是我们持久化配置的关键文件。
具体如下:
这里讲几个主要的参数,
org.quartz.scheduler.instanceName = DefaultQuartzScheduler 调度器名称,默认名称,假如是使用上面bean加载的方式,那么调度器名称将会是bean的名称
org.quartz.threadPool.threadCount = 5 并发个数,最小为1,否则会报错,但不能配置过大,比如100什么的,太多的闲置线程将影响工程的性能,
org.quartz.threadPool.threadPriority = 5 优先级
org.quartz.jobStore.tablePrefix = QRTZ_ 数据表前缀,改这个你能自定义一套自己的数据表,
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore # 默认存储在内存中,启用这个代表不使用持久化,定时任务的生命周期和工程的开启结束一致
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX #持久化存储,和内存存储只能启用一个,好处是配置的任务能永久保存。
# 固定前缀org.quartz
# 主要分为scheduler、threadPool、jobStore、plugin等部分
#
#
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
# 实例化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
# 默认存储在内存中
#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
#持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.tablePrefix = QRTZ_
#org.quartz.jobStore.dataSource = qzDS1
#org.quartz.dataSource.qzDS1.connectionProvider.class=com.SpringConnectionProvider
org.quartz.jobStore.dataSource = qzDS
#假如已经在bean里声明了数据源,以下四句可以直接注释掉,二选一配置数据源
#org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver
#org.quartz.dataSource.qzDS.URL = jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8
#org.quartz.dataSource.qzDS.user = root
#org.quartz.dataSource.qzDS.password = 123456
其中这里的数据源可以自行配置,这就是第二种配置数据源的方式,假如已经在bean里声明了数据源,以下四句可以直接注释掉,二选一配置数据源
#org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver
#org.quartz.dataSource.qzDS.URL = jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8
#org.quartz.dataSource.qzDS.user = root
#org.quartz.dataSource.qzDS.password = 123456
第三种配置数据源的方式,同样的将其他配置数据源的代码注释掉,启用如下
org.quartz.jobStore.dataSource = qzDS1
org.quartz.dataSource.qzDS1.connectionProvider.class=com.SpringConnectionProvider
这里自定义了一个数据源名称为 qzDS1 ,数据源指向了com.SpringConnectionProvider
具体数据源配置为 com.SpringConnectionProvider 这个是类的路径名称。如下:
具体代码如下:
这种好处是,你可以在这里添加些你自己的处理逻辑
package com;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
import org.quartz.utils.ConnectionProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SpringConnectionProvider implements ConnectionProvider {
private static final Logger LOG = LoggerFactory.getLogger(SpringConnectionProvider.class);
private DataSource dataSource;
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public void shutdown() throws SQLException {
LOG.debug("==========do shutdown===========");
dataSource = null;
}
public void initialize() throws SQLException {
LOG.debug("==========do initialize===========使用配置中心定义的数据源");
BasicDataSource da = new BasicDataSource();
da.setDriverClassName("com.mysql.jdbc.Driver");
da.setUrl("jdbc:mysql://10.10.0.239:3306/lhywzx_dev?characterEncoding=utf8");
da.setUsername("taxcp");
da.setPassword("taxcp1234");
this.dataSource = da;
}
}
四、创建数据库
官方数据库,下面是mysql的
CREATE TABLE QRTZ_JOB_DETAILS
(
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE VARCHAR(1) NOT NULL,
IS_NONCONCURRENT VARCHAR(1) NOT NULL,
IS_UPDATE_DATA VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);
CREATE TABLE QRTZ_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT(13) NULL,
PREV_FIRE_TIME BIGINT(13) NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT(13) NOT NULL,
END_TIME BIGINT(13) NULL,
CALENDAR_NAME VARCHAR(200) NULL,
MISFIRE_INSTR SMALLINT(2) NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
);
CREATE TABLE QRTZ_SIMPLE_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
REPEAT_COUNT BIGINT(7) NOT NULL,
REPEAT_INTERVAL BIGINT(12) NOT NULL,
TIMES_TRIGGERED BIGINT(10) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_CRON_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CRON_EXPRESSION VARCHAR(200) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_SIMPROP_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
STR_PROP_1 VARCHAR(512) NULL,
STR_PROP_2 VARCHAR(512) NULL,
STR_PROP_3 VARCHAR(512) NULL,
INT_PROP_1 INT NULL,
INT_PROP_2 INT NULL,
LONG_PROP_1 BIGINT NULL,
LONG_PROP_2 BIGINT NULL,
DEC_PROP_1 NUMERIC(13,4) NULL,
DEC_PROP_2 NUMERIC(13,4) NULL,
BOOL_PROP_1 VARCHAR(1) NULL,
BOOL_PROP_2 VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_BLOB_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_CALENDARS
(
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_FIRED_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
FIRED_TIME BIGINT(13) NOT NULL,
SCHED_TIME BIGINT(13) NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(200) NULL,
JOB_GROUP VARCHAR(200) NULL,
IS_NONCONCURRENT VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);
CREATE TABLE QRTZ_SCHEDULER_STATE
(
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);
CREATE TABLE QRTZ_LOCKS
(
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);
commit;
5:进行增删查改等操作
其实就是最基本的增删改的操作,和其他人的差不多,有微调
首先我们这里使用我们注入之前配置的bean, 如果你想要和其他区分开来,你叫scheduler_test都行。
@Autowired
private Scheduler scheduler;
package com;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.alibaba.fastjson.JSONObject;
import com.example.demo.entity.JobAndTrigger;
import com.example.demo.job.BaseJob;
import com.example.demo.service.IJobAndTriggerService;
import com.github.pagehelper.PageInfo;
@Controller
@RequestMapping(value="/job")
public class JobController
{
@Autowired
private Scheduler scheduler;
private static Logger log = LoggerFactory.getLogger(JobController.class);
@ResponseBody
@RequestMapping(value="/addjob")
public void addjob(@RequestParam(value="jobClassName")String jobClassName,
@RequestParam(value="jobGroupName")String jobGroupName,
@RequestParam(value="cronExpression")String cronExpression) throws Exception
{
addJob(jobClassName, jobGroupName, cronExpression);
}
@ResponseBody
@RequestMapping(value="/getindex.do")
public String getindex() throws Exception
{
return "index";
}
public void addJob(String jobClassName, String jobGroupName, String cronExpression)throws Exception{
// 启动调度器
scheduler.start();
//构建job信息
JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass()).withIdentity(jobClassName, jobGroupName).build();
//表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
//按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger()
.withSchedule(scheduleBuilder).build();
try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
System.out.println("创建定时任务失败"+e);
throw new Exception("创建定时任务失败");
}
}
@ResponseBody
@RequestMapping(value="/pausejob")
public void pausejob(@RequestParam(value="jobClassName")String jobClassName, @RequestParam(value="jobGroupName")String jobGroupName) throws Exception
{
jobPause(jobClassName, jobGroupName);
}
public void jobPause(String jobClassName, String jobGroupName) throws Exception
{
scheduler.pauseJob(JobKey.jobKey(jobClassName, jobGroupName));
}
@ResponseBody
@RequestMapping(value="/resumejob")
public void resumejob(@RequestParam(value="jobClassName")String jobClassName, @RequestParam(value="jobGroupName")String jobGroupName) throws Exception
{
jobresume(jobClassName, jobGroupName);
}
public void jobresume(String jobClassName, String jobGroupName) throws Exception
{
scheduler.resumeJob(JobKey.jobKey(jobClassName, jobGroupName));
}
@ResponseBody
@RequestMapping(value="/reschedulejob")
public void rescheduleJob(@RequestParam(value="jobClassName")String jobClassName,
@RequestParam(value="jobGroupName")String jobGroupName,
@RequestParam(value="cronExpression")String cronExpression) throws Exception
{
jobreschedule(jobClassName, jobGroupName, cronExpression);
}
public void jobreschedule(String jobClassName, String jobGroupName, String cronExpression) throws Exception
{
try {
TriggerKey triggerKey = TriggerKey.triggerKey(jobClassName, jobGroupName);
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
// 按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
System.out.println("更新定时任务失败"+e);
throw new Exception("更新定时任务失败");
}
}
@ResponseBody
@RequestMapping(value="/deletejob")
public void deletejob(@RequestParam(value="jobClassName")String jobClassName, @RequestParam(value="jobGroupName")String jobGroupName) throws Exception
{
jobdelete(jobClassName, jobGroupName);
}
public void jobdelete(String jobClassName, String jobGroupName) throws Exception
{
scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.deleteJob(JobKey.jobKey(jobClassName, jobGroupName));
}
public static BaseJob getClass(String classname) throws Exception
{
Class<?> class1 = Class.forName(classname);
return (BaseJob)class1.newInstance();
}
}
查看数据库结果,正常写入,所有动作都被监听到了