1.基础环境配置
1.1 quartz调度框架内置表
进入quartz的官网http://www.quartz-scheduler.org/,点击Downloads。在数据库运行quartz目录\docs\dbTables下的 tables_mysql.sql
1.2pom依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
2.动态定时任务配置实现
2.1 自定义业务表
-- 注意:job_name存放的任务类的全路径,在quartz中通过jobName和jobGroup来确定trigger的唯一性,所以这两列为联合唯一索引
create table t_schedule_trigger
(
id int primary key auto_increment, -- ID
cron varchar(200) not null, -- 时间表达式
status char(1) not null, -- 使用状态 0:禁用 1:启用
job_name varchar(200) not null, -- 任务名称
job_group varchar(200) not null, -- 任务分组
unique index(job_name,job_group)
);
-- 额外添加到任务中的参数
create table t_schedule_trigger_param
(
param_id int primary key auto_increment, -- ID
name varchar(200) not null, -- 参数名
value varchar(512), -- 参数值
schedule_trigger_id int not null, -- 外键:引用t_schedule_trigger(id)
foreign key(schedule_trigger_id) references t_schedule_trigger(id)
);
2.2quartz.properties(更改默认数据源)
org.quartz.scheduler.instanceName:DefaultQuartzScheduler
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
org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties:true
org.quartz.jobStore.tablePrefix:qrtz_
org.quartz.jobStore.dataSource:qzDS
org.quartz.jobStore.isClustered=true
org.quartz.dataSource.qzDS.connectionProvider.class:com.wyy.cp.util.DruidConnectionProvider
org.quartz.dataSource.qzDS.driver:com.mysql.cj.jdbc.Driver
org.quartz.dataSource.qzDS.URL:jdbc:mysql://127.0.0.1:3306/eshop?useUnicode=true&serverTimezone=Asia/Shanghai&useSSL=false&characterEncoding=utf-8
org.quartz.dataSource.qzDS.user:root
org.quartz.dataSource.qzDS.password:123456
org.quartz.dataSource.qzDS.maxConnection:10
2.3 DruidConnectionProvider.java(数据源拓展类)
package com.wyy.cp.util;
import com.alibaba.druid.pool.DruidDataSource;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.quartz.utils.ConnectionProvider;
import java.sql.Connection;
/*
#============================================================================
# JDBC
#============================================================================
org.quartz.jobqzDS.connectionProvider.class:com.zking.q03.quartz.DruidConnectionProvider
org.quartz.dataSourStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties:false
org.quartz.jobStore.dataSource:qzDS
#org.quartz.dataSource.qzDS.connectionProvider.class:org.quartz.utils.PoolingConnectionProvider
org.quartz.dataSource.ce.qzDS.driver:com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL:jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8
org.quartz.dataSource.qzDS.user:root
org.quartz.dataSource.qzDS.password:root
org.quartz.dataSource.qzDS.maxConnections:30
org.quartz.dataSource.qzDS.validationQuery: select 0
*/
/**
* Druid连接池的Quartz扩展类
*
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DruidConnectionProvider implements ConnectionProvider {
/**
* 常量配置与quartz.properties文件的key保持一致(去掉前缀)
* 同时提供set方法,Quartz框架自动注入值.
*/
/**
* JDBC驱动
*/
public String driver;
/**
* JDBC连接串
*/
public String URL;
/**
* 数据库用户名
*/
public String user;
/**
* 数据库用户密码
*/
public String password;
/**
* 数据库最大连接数
*/
public int maxConnection;
/**
* 数据库SQL查询每次连接返回执行到连接池,以确保它仍然是有效的
*/
public String validationQuery;
private boolean validateOnCheckout;
private int idleConnectionValidationSeconds;
public String maxCachedStatementsPerConnection;
private String discardIdleConnectionsSeconds;
public static final int DEFAULT_DB_MAX_CONNECTIONS = 10;
public static final int DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION = 120;
/**
* Druid连接池
*/
private DruidDataSource datasource;
@Override
@SneakyThrows
public Connection getConnection() {
return datasource.getConnection();
}
@Override
public void shutdown() {
datasource.close();
}
@Override
@SneakyThrows
public void initialize() {
assert this.URL != null : "DB URL cannot be null";
assert this.driver != null : "DB driver class name cannot be null!";
assert this.maxConnection > 0 : "Max connections must be greater than zero!";
datasource = new DruidDataSource();
datasource.setDriverClassName(this.driver);
datasource.setUrl(this.URL);
datasource.setUsername(this.user);
datasource.setPassword(this.password);
datasource.setMaxActive(this.maxConnection);
datasource.setMinIdle(1);
datasource.setMaxWait(0);
datasource.setMaxPoolPreparedStatementPerConnectionSize(DruidConnectionProvider.DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION);
if (this.validationQuery != null) {
datasource.setValidationQuery(this.validationQuery);
if (!this.validateOnCheckout) {
datasource.setTestOnReturn(true);
} else {
datasource.setTestOnBorrow(true);
}
datasource.setValidationQueryTimeout(this.idleConnectionValidationSeconds);
}
}
}
2.4 MyJobFactory.java(解决自定义作业类被spring管理的问题)
package com.wyy.cp.util;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
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
@Slf4j
public class MyJobFactory extends AdaptableJobFactory {
private final AutowireCapableBeanFactory autowireCapableBeanFactory;
@Autowired
public MyJobFactory(AutowireCapableBeanFactory autowireCapableBeanFactory) {
this.autowireCapableBeanFactory = autowireCapableBeanFactory;
}
/**
* 重写创建Job任务的实例方法,解决Job任务无法使用Spring中的Bean问题
*/
@Override
@SneakyThrows
protected Object createJobInstance(TriggerFiredBundle bundle) {
Object jobInstance = super.createJobInstance(bundle);
autowireCapableBeanFactory.autowireBean(jobInstance);
return super.createJobInstance(bundle);
}
}
2.5 QuartzConfiguration.java(quartz调度框架与spring框架整合的配置类,主要是要将org.quartz.Scheduler交给spring进行管理)
package com.wyy.cp.util;
import lombok.SneakyThrows;
import org.quartz.Scheduler;
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 java.util.Properties;
@Configuration
public class QuartzConfiguration {
private final MyJobFactory myJobFactory;
@Autowired
public QuartzConfiguration(MyJobFactory myJobFactory) {
this.myJobFactory = myJobFactory;
}
/**
* 创建调度器工厂
*/
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
//1.创建SchedulerFactoryBean
SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
//2.加载自定义的quartz.properties配置文件
factoryBean.setQuartzProperties(quartzProperties());
//3.设置MyJobFactory
factoryBean.setJobFactory(myJobFactory);
return factoryBean;
}
@Bean
@SneakyThrows
public Properties quartzProperties() {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
@Bean
public Scheduler scheduler() {
return schedulerFactoryBean().getScheduler();
}
}
2.6 application.yml
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/eshop?useUnicode=true&serverTimezone=Asia/Shanghai&useSSL=false&characterEncoding=utf-8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 5 # 初始化大小
min-idle: 10 # 最小连接数
max-active: 20 # 最大连接数
max-wait: 60000 # 获取连接时的最大等待时间
min-evictable-idle-time-millis: 300000 # 一个连接在池中最小生存的时间,单位是毫秒
time-between-eviction-runs-millis: 60000 # 多久才进行一次检测需要关闭的空闲连接,单位是毫秒
filters: stat # 配置扩展插件:stat-监控统计,log4j-日志,wall-防火墙(防止SQL注入),去掉后,监控界面的sql无法统计 ,wall
validation-query: SELECT 1 # 检测连接是否有效的 SQL语句,为空时以下三个配置均无效
test-on-borrow: true # 申请连接时执行validationQuery检测连接是否有效,默认true,开启后会降低性能
test-on-return: true # 归还连接时执行validationQuery检测连接是否有效,默认false,开启后会降低性能
test-while-idle: true # 申请连接时如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效,默认false,建议开启,不影响性能
stat-view-servlet:
enabled: true # 是否开启 StatViewServlet
allow: 127.0.0.1 # 访问监控页面 白名单,默认127.0.0.1
deny: 192.168.56.1 # 访问监控页面 黑名单
login-username: admin # 访问监控页面 登陆账号
login-password: 123 # 访问监控页面 登陆密码
filter:
stat:
enabled: true # 是否开启 FilterStat,默认true
log-slow-sql: true # 是否开启 慢SQL 记录,默认false
slow-sql-millis: 5000 # 慢 SQL 的标准,默认 3000,单位:毫秒
merge-sql: false # 合并多个连接池的监控数据,默认false
freemarker:
#指定HttpServletRequest的属性是否可以覆盖controller的model的同名项
allow-request-override: false
#req访问request
request-context-attribute: req
#后缀名freemarker默认后缀为.ftl,当然你也可以改成自己习惯的.html
suffix: .ftl
#设置响应的内容类型
content-type: text/html;charset=utf-8
#是否允许mvc使用freemarker
enabled: true
#是否开启template caching
cache: false
#设定模板的加载路径,多个以逗号分隔,默认: [“classpath:/templates/”]
template-loader-path: classpath:/templates/
#设定Template的编码
charset: UTF-8
logging:
level:
com.wyy.cp.mapper: debug
2.7 任务类
package com.wyy.cp.jop;
import ch.qos.logback.core.net.SyslogOutputStream;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class Myjob implements Job {
@Override
public void execute(JobExecutionContext Context) throws JobExecutionException {
Object name = Context.getJobDetail().getJobDataMap().get("name");
Object add = Context.getJobDetail().getJobDataMap().get("add");
System.out.println(name+"在搞"+add+"卫生!!!!!!!");
}
}
3 业务代码
3.1 ScheduleTriggerDataMapper.java
package com.wyy.cp.mapper;
import com.wyy.cp.pojo.ScheduleTriggerData;
import org.springframework.stereotype.Repository;
import tk.mybatis.mapper.common.Mapper;
@Repository
public interface ScheduleTriggerDataMapper extends Mapper<ScheduleTriggerData> {
}
3.2 ScheduleTriggerMapper .java
package com.wyy.cp.mapper;
import com.wyy.cp.pojo.ScheduleTrigger;
import org.springframework.stereotype.Repository;
import tk.mybatis.mapper.common.Mapper;
@Repository
public interface ScheduleTriggerMapper extends Mapper<ScheduleTrigger> {
}
3.3 ScheduleTriggerDataServiceImpl.java
package com.wyy.cp.Service;
import com.wyy.cp.mapper.ScheduleTriggerDataMapper;
import com.wyy.cp.pojo.ScheduleTriggerData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;
import java.util.List;
@Service
@Primary
public class ScheduleTriggerDataServiceImpl implements ScheduleTriggerDataService {
@Autowired
private ScheduleTriggerDataMapper TriggerDataMapper;
@Override
public List<ScheduleTriggerData> getdata(Long TriggerId) {
Example example=new Example(ScheduleTriggerData.class);
example.createCriteria().andEqualTo("triggerId",TriggerId);
return TriggerDataMapper.selectByExample(example);
}
}
3.4 ScheduleTriggerMapper .java
package com.wyy.cp.Service;
import com.wyy.cp.mapper.ScheduleTriggerMapper;
import com.wyy.cp.pojo.ScheduleTrigger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@Primary
public class ScheduleTriggerServiceImpl implements ScheduleTriggerService {
@Autowired
private ScheduleTriggerMapper triggerMapper;
@Override
public List<ScheduleTrigger> getAll() {
return triggerMapper.selectAll();
}
}
4 动态定时任务实现
package com.wyy.cp.util;
import com.wyy.cp.Service.ScheduleTriggerDataService;
import com.wyy.cp.Service.ScheduleTriggerService;
import com.wyy.cp.pojo.ScheduleTrigger;
import com.wyy.cp.pojo.ScheduleTriggerData;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.xml.ws.soap.Addressing;
import java.util.List;
@Component
public class QuartzTask {
@Autowired
private ScheduleTriggerService triggerService;
@Autowired
private ScheduleTriggerDataService triggerDataService;
@Autowired
private Scheduler scheduler;
@Scheduled(cron = "0/10 * * * * ?")
public void select() throws Exception{
List<ScheduleTrigger> all = triggerService.getAll();
for (ScheduleTrigger t: all) {
//判断是否被Quartz管理
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(TriggerKey.triggerKey(t.getJobName(), t.getJobGroup()));
if(trigger==null){
if(t.getStatus().equals("1")){
JobDetail jobDetail=JobBuilder
.newJob((Class<? extends Job>) Class.forName(t.getJobName()))
.withIdentity(t.getJobName(),t.getJobGroup())
.build();
JobDataMap jobDataMap = jobDetail.getJobDataMap();
List<ScheduleTriggerData> getdata = triggerDataService.getdata(t.getId());
for (ScheduleTriggerData data:getdata ) {
jobDataMap.put(data.getName(),data.getValue());
}
trigger= TriggerBuilder.newTrigger()
.withIdentity(t.getJobName(),t.getJobGroup())
.withSchedule(CronScheduleBuilder.cronSchedule(t.getCron()))
.build();
scheduler.scheduleJob(jobDetail,trigger);
}
}
}
}
}