1.项目结构
这篇文章是对上篇文章《Spring Boot Quartz 动态任务实现方式》的稍作变动, 但最终实现效果是一致的。
持久层变动:Spring Boot使用spring data jpa, Spring MVC 使用 Mybatis
2.Maven
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.littlefxc</groupId>
<artifactId>learn-quartz-SpringMVC</artifactId>
<version>1.0-snapshot</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-framework-bom.version>5.0.8.RELEASE</spring-framework-bom.version>
<mysql.verison>5.1.47</mysql.verison>
<druid.version>1.1.10</druid.version>
<quartz.version>2.3.0</quartz.version>
<spring-data.version>2.0.9.RELEASE</spring-data.version>
<junit.version>4.12</junit.version>
<slf4j.version>1.7.25</slf4j.version>
<log4j2.version>2.11.1</log4j2.version>
<mybatis.version>3.4.6</mybatis.version>
<mybatis-spring.version>1.3.2</mybatis-spring.version>
<aopalliance.version>1.0</aopalliance.version>
<pagehelper.version>5.1.8</pagehelper.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>${spring-framework-bom.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>${aopalliance.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!-- quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>${quartz.version}</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>${quartz.version}</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!-- mybatis/spring包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
<!-- pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>${pagehelper.version}</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.verison}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<!-- 日志管理 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>${log4j2.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j2.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j2.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
<version>${log4j2.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<!-- JSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.7</version>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
<!-- 默认源代码和资源文件目录配置 -->
<sourceDirectory>src/main/java</sourceDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
</project>
3.数据库-模型
在jar包quartz-2.3.0.jar下有数据库sql文件.
sql文件的包路径地址:org.quartz.impl.jdbcjobstore,选择tables_mysql_innodb.sql
3.1. scheduler_job_info.sql
DROP TABLE IF EXISTS `scheduler_job_info`;
CREATE TABLE `scheduler_job_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`cron_expression` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`cron_job` bit(1) NULL DEFAULT NULL,
`job_class` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`job_group` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`job_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`scheduler_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`repeat_time` bigint(20) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_job_name`(`job_name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
3.2.实体类
package com.littlefxc.example.quartz.enitiy;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
/**
* @author fengxuechao
* @date 12/19/2018
*/
@Data
@Entity
@Table(name = "scheduler_job_info")
public class SchedulerJob implements Serializable {
private static final long serialVersionUID = -8990533448070839127L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String jobName;
private String jobGroup;
private String jobClass;
private String cronExpression;
private Long repeatTime;
private Boolean cronJob;
private String schedulerName;
}
4.配置
##4.1.quartz.properties
文件应用在spring-context.xml中,详见4.3章节黄色高亮代码。
org.quartz.scheduler.instanceIdGenerator.class=com.littlefxc.example.quartz.component.CustomQuartzInstanceIdGenerator
表示自定义的实例名生成策略,该类代码可以在5.1章节中看到,在数据库上的代码实际效果可以查看到(表qrtz_scheduler_state
, 字段INSTANCE_NAME
)。
请注意对比与上篇文章《Spring Boot Quartz 动态任务实现方式》中4.1章节中的配置(application.properties)以spring.quartz.properties为前缀的属性。两者之间的区别仅在于SpringBoot以spring.quartz.properties为前缀。
# 属性 org.quartz.impl.StdSchedulerFactory
# 属性可为任何值,(最好别用DefaultQuartzScheduler),用在 JDBC JobStore 中来唯一标识实例,但是所有集群节点中必须相同
org.quartz.scheduler.instanceName=lean-quartz-SpringMVC
org.quartz.scheduler.instanceId=AUTO
org.quartz.scheduler.instanceIdGenerator.class=com.littlefxc.example.quartz.component.CustomQuartzInstanceIdGenerator
org.quartz.threadPool.threadCount=20
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.misfireThreshold=60000
org.quartz.jobStore.tablePrefix=qrtz_
# 集群配置
org.quartz.jobStore.isClustered=true
org.quartz.plugin.shutdownHook.class=org.quartz.plugins.management.ShutdownHookPlugin
org.quartz.plugin.shutdownHook.cleanShutdown=TRUE
4.2.spring-context.xml
spring-tx.xml, spring-mvc.xml的配置不是重点,以免浪费篇幅,按照你自己的个人习惯来配置.
同时我也将Druid数据源的配置省略了,请重点查看有黄色高亮的bean配置,参照上篇文章《Spring Boot Quartz 动态任务实现方式》中的配置
spring-tx.xml :配置Spring事务.
spring-mvc.xml:配置spring mvc.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<context:component-scan base-package="com.littlefxc.example.quartz.service"/>
<context:component-scan base-package="com.littlefxc.example.quartz.dao"/>
<context:component-scan base-package="com.littlefxc.example.quartz.component"/>
<context:property-placeholder location="classpath:druid.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
... Druid数据源的配置
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
<property name="typeAliasesPackage" value="com.littlefxc.example.quartz.enitiy" />
<property name="mapperLocations" value="classpath:mybatis/mapper/*.xml" />
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<!-- 参考链接:https://pagehelper.github.io/docs/howtouse/#1-%E5%BC%95%E5%85%A5%E5%88%86%E9%A1%B5%E6%8F%92%E4%BB%B6 -->
<property name="properties">
<value>
helperDialect=mysql
reasonable=true
supportMethodsArguments=true
params=count=countSql
rowBoundsWithCount=true
autoRuntimeDialect=true
</value>
</property>
</bean>
</array>
</property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="com.littlefxc.example.quartz.dao" />
</bean>
<!-- 创建SchedulerFactoryBean,参考模块learn-quartz-SpringBoot#com.littlefxc.example.quartz.config.SchedulerConfig -->
<bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jobFactory" ref="jobFactory"/>
<property name="schedulerName" value="scheduler-spring-mvc"/>
<!--可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 -->
<property name="overwriteExistingJobs" value="true"/>
<!--可以在web关闭的时候关闭线程-->
<property name="waitForJobsToCompleteOnShutdown" value="true"/>
<property name="configLocation" value="classpath:quartz.properties"/>
</bean>
<bean id="jobFactory" class="com.littlefxc.example.quartz.component.SchedulerJobFactory" />
<import resource="spring-tx.xml"/>
<import resource="spring-mvc.xml"/>
</beans>
5.组件
##5.1.CustomQuartzInstanceIdGenerator
用法详见4.1章节
package com.littlefxc.example.quartz.component;
import org.quartz.SchedulerException;
import org.quartz.spi.InstanceIdGenerator;
import java.util.UUID;
/**
* @author fengxuechao
* @date 12/19/2018
*/
public class CustomQuartzInstanceIdGenerator implements InstanceIdGenerator {
@Override
public String generateInstanceId() throws SchedulerException {
try {
return UUID.randomUUID().toString();
} catch (Exception ex) {
throw new SchedulerException("Couldn't generate UUID!", ex);
}
}
}
5.2.SchedulerJobFactory
Quartz与Spring结合。
在SchedulerFactory中引入Spring上下文。
用法详见4.2章节。
package com.littlefxc.example.quartz.component;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
/**
* 模仿了:{@link org.springframework.boot.autoconfigure.quartz.AutowireCapableBeanJobFactory}
*
* @author fengxuechao
* @date 12/19/2018
* @see <a href="http://blog.btmatthews.com/?p=40#comment-33797">注入Spring上下文(applicationContext)
*/
public class SchedulerJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
private AutowireCapableBeanFactory beanFactory;
@Override
public void setApplicationContext(final ApplicationContext context) {
beanFactory = context.getAutowireCapableBeanFactory();
}
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
}
5.3.JobScheduleCreator
Scheduler 创建Job,SimpleTrigger,CronTrigger的封装类。
用法在service 层体现。
package com.littlefxc.example.quartz.component;
import lombok.extern.slf4j.Slf4j;
import org.quartz.CronTrigger;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.SimpleTrigger;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean;
import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.util.Date;
/**
* Scheduler创建Job, SimpleTrigger, CronTrigger
*
* @author fengxuechao
* @date 12/19/2018
* @see <a href="https://blog.csdn.net/yangshangwei/article/details/78539433#withmisfirehandlinginstructiondonothing">Quartz-错过触发机制</a>
*/
@Slf4j
@Component
public class JobScheduleCreator {
/**
* Create Quartz Job.
*
* @param jobClass Class whose executeInternal() method needs to be called.
* @param isDurable Job needs to be persisted even after completion. if true, job will be persisted, not otherwise.
* @param context Spring application context.
* @param jobName Job name.
* @param jobGroup Job group.
* @return JobDetail object
*/
public JobDetail createJob(Class<? extends QuartzJobBean> jobClass, boolean isDurable,
ApplicationContext context, String jobName, String jobGroup) {
JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
factoryBean.setJobClass(jobClass);
factoryBean.setDurability(isDurable);
factoryBean.setApplicationContext(context);
factoryBean.setName(jobName);
factoryBean.setGroup(jobGroup);
// set job data map
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put(jobName + jobGroup, jobClass.getName());
factoryBean.setJobDataMap(jobDataMap);
factoryBean.afterPropertiesSet();
return factoryBean.getObject();
}
/**
* Create cron trigger.
*
* @param triggerName Trigger name.
* @param startTime Trigger start time.
* @param cronExpression Cron expression.
* @param misFireInstruction Misfire instruction (what to do in case of misfire happens).
* @return {@link CronTrigger}
*/
public CronTrigger createCronTrigger(String triggerName, Date startTime, String cronExpression, int misFireInstruction) {
CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean();
factoryBean.setName(triggerName);
factoryBean.setStartTime(startTime);
factoryBean.setCronExpression(cronExpression);
factoryBean.setMisfireInstruction(misFireInstruction);
try {
factoryBean.afterPropertiesSet();
} catch (ParseException e) {
log.error(e.getMessage(), e);
}
return factoryBean.getObject();
}
/**
* Create simple trigger.
*
* @param triggerName Trigger name.
* @param startTime Trigger start time.
* @param repeatTime Job repeat period mills
* @param misFireInstruction Misfire instruction (what to do in case of misfire happens).
* @return {@link SimpleTrigger}
*/
public SimpleTrigger createSimpleTrigger(String triggerName, Date startTime, Long repeatTime, int misFireInstruction) {
SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
factoryBean.setName(triggerName);
factoryBean.setStartTime(startTime);
factoryBean.setRepeatInterval(repeatTime);
factoryBean.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
factoryBean.setMisfireInstruction(misFireInstruction);
factoryBean.afterPropertiesSet();
return factoryBean.getObject();
}
}
6.Jobs
Simple Job
package com.littlefxc.example.quartz.jobs;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.util.stream.IntStream;
/**
* @author fengxuechao
* @date 12/19/2018
*/
@Slf4j
public class SimpleJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
log.info("{} Start................", context.getJobDetail().getKey());
IntStream.range(0, 5).forEach(i -> {
log.info("Counting - {}", i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.error(e.getMessage(), e);
}
});
log.info("{} End................", context.getJobDetail().getKey());
}
}
Cron Job
package com.littlefxc.example.quartz.jobs;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.util.stream.IntStream;
/**
* @author fengxuechao
* @date 12/19/2018
*/
@Slf4j
@DisallowConcurrentExecution // 这个注解告诉Quartz,一个给定的Job定义(也就是一个JobDetail实例),不并发运行。
public class SampleCronJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
log.info("{} Start................", context.getJobDetail().getKey());
IntStream.range(0, 10).forEach(i -> {
log.info("Counting - {}", i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.error(e.getMessage(), e);
}
});
log.info("{} End................", context.getJobDetail().getKey());
}
}
7.控制器层
7.1.QuartzController
工作调度的主要代码。
与上篇文章《Spring Boot Quartz 动态任务实现方式》的不同点在于queryjob()方法,直接使用了pageNum, pageSize而不是Pageable(Spring data jpa 自带分页类)
package com.littlefxc.example.quartz.controller;
import com.github.pagehelper.PageInfo;
import com.littlefxc.example.quartz.enitiy.SchedulerJob;
import com.littlefxc.example.quartz.service.SchedulerService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* @author fengxuechao
* @date 12/19/2018
**/
@RestController
@RequestMapping("/job")
@Slf4j
public class QuartzController {
private final SchedulerService schedulerService;
@Autowired
public QuartzController(SchedulerService schedulerService) {
this.schedulerService = schedulerService;
}
/**
* 添加
*
* @param jobInfo
*/
@PostMapping(value = "/addjob")
public void addjob(@RequestBody SchedulerJob jobInfo) {
schedulerService.scheduleNewJob(jobInfo);
}
/**
* 暂停
*
* @param jobName
* @param jobGroup
*/
@PostMapping(value = "/pausejob")
public void pausejob(
@RequestParam String jobName, @RequestParam String jobGroup) {
schedulerService.pauseJob(jobName, jobGroup);
}
/**
* 恢复启动
*
* @param jobName
* @param jobGroup
*/
@PostMapping(value = "/resumejob")
public void resumejob(@RequestParam String jobName, @RequestParam String jobGroup) {
schedulerService.resumeJob(jobName, jobGroup);
}
/**
* 更新:移除older trigger,添加new trigger
*
* @param jobInfo
*/
@PostMapping(value = "/reschedulejob")
public void rescheduleJob(@RequestBody SchedulerJob jobInfo) {
schedulerService.updateScheduleJob(jobInfo);
}
/**
* 删除
*
* @param jobName
* @param jobGroup
*/
@PostMapping(value = "/deletejob")
public void deletejob(@RequestParam String jobName, @RequestParam String jobGroup) {
schedulerService.deleteJob(jobName, jobGroup);
}
/**
* 查询
*
* @param pageNum default 1
* @param pageSize default 10
* @param cron true:cron trigger, false:simple trigger
* @return
*/
@GetMapping(value = "/queryjob")
public PageInfo<Map<String, Object>> queryjob(
@RequestParam(defaultValue = "0") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam Boolean cron) {
return schedulerService.findAll(cron, pageNum, pageSize);
}
}
7.2.SchedulerController
仅对自定义数据库(scheduler_job_info)操作的控制器。
package com.littlefxc.example.quartz.controller;
import com.littlefxc.example.quartz.enitiy.SchedulerJob;
import com.littlefxc.example.quartz.service.SchedulerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author fengxuechao
* @date 12/20/2018
**/
@RestController
@RequestMapping("/job-info")
public class SchedulerController {
@Autowired
private SchedulerService schedulerService;
/**
* 根据jobName查询
* @param jobName
* @return {@link SchedulerJob}
*/
@GetMapping("/findOne")
public SchedulerJob findOne(@RequestParam String jobName) {
return schedulerService.findOne(jobName);
}
}
8.Service层
##8.1.SchedulerServiceImpl
package com.littlefxc.example.quartz.service.impl;
import com.github.pagehelper.PageInfo;
import com.littlefxc.example.quartz.component.JobScheduleCreator;
import com.littlefxc.example.quartz.dao.SchedulerDao;
import com.littlefxc.example.quartz.enitiy.SchedulerJob;
import com.littlefxc.example.quartz.service.SchedulerService;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.RowBounds;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* @author fengxuechao
* @date 12/19/2018
*/
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class SchedulerServiceImpl implements SchedulerService {
@Autowired
private SchedulerFactoryBean schedulerFactoryBean;
@Autowired
private SchedulerDao schedulerDao;
@Autowired
private ApplicationContext context;
@Autowired
private JobScheduleCreator scheduleCreator;
/**
* 启动所有的在表scheduler_job_info中记录的job
*/
@Override
public void startAllSchedulers() {
List<SchedulerJob> jobInfoList = schedulerDao.findAll();
if (jobInfoList != null) {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
jobInfoList.forEach(jobInfo -> {
try {
JobDetail jobDetail = JobBuilder.newJob((Class<? extends QuartzJobBean>) Class.forName(jobInfo.getJobClass()))
.withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup()).build();
if (!scheduler.checkExists(jobDetail.getKey())) {
Trigger trigger;
jobDetail = scheduleCreator.createJob((Class<? extends QuartzJobBean>) Class.forName(jobInfo.getJobClass()),
false, context, jobInfo.getJobName(), jobInfo.getJobGroup());
if (jobInfo.getCronJob() && CronExpression.isValidExpression(jobInfo.getCronExpression())) {
trigger = scheduleCreator.createCronTrigger(jobInfo.getJobName(), new Date(),
jobInfo.getCronExpression(), CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING);
} else {
trigger = scheduleCreator.createSimpleTrigger(jobInfo.getJobName(), new Date(),
jobInfo.getRepeatTime(), SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT);
}
scheduler.scheduleJob(jobDetail, trigger);
}
} catch (ClassNotFoundException e) {
log.error("Class Not Found - {}", jobInfo.getJobClass(), e);
} catch (SchedulerException e) {
log.error(e.getMessage(), e);
}
});
}
}
@Override
public void scheduleNewJob(SchedulerJob jobInfo) {
try {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
JobDetail jobDetail = JobBuilder.newJob((Class<? extends QuartzJobBean>) Class.forName(jobInfo.getJobClass()))
.withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup()).build();
if (!scheduler.checkExists(jobDetail.getKey())) {
jobDetail = scheduleCreator.createJob((Class<? extends QuartzJobBean>) Class.forName(jobInfo.getJobClass()),
false, context, jobInfo.getJobName(), jobInfo.getJobGroup());
Trigger trigger;
if (jobInfo.getCronJob()) {
trigger = scheduleCreator.createCronTrigger(jobInfo.getJobName(), new Date(), jobInfo.getCronExpression(),
CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING);
} else {
trigger = scheduleCreator.createSimpleTrigger(jobInfo.getJobName(), new Date(), jobInfo.getRepeatTime(),
SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT);
}
scheduler.scheduleJob(jobDetail, trigger);
jobInfo.setSchedulerName(schedulerFactoryBean.getScheduler().getSchedulerName());
schedulerDao.save(jobInfo);
} else {
log.error("scheduleNewJobRequest.jobAlreadyExist");
}
} catch (ClassNotFoundException e) {
log.error("Class Not Found - {}", jobInfo.getJobClass(), e);
} catch (SchedulerException e) {
log.error(e.getMessage(), e);
}
}
@Override
public void updateScheduleJob(SchedulerJob jobInfo) {
Trigger newTrigger;
System.out.println(jobInfo);
if (jobInfo.getCronJob()) {
newTrigger = scheduleCreator.createCronTrigger(jobInfo.getJobName(), new Date(), jobInfo.getCronExpression(),
CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING);
} else {
newTrigger = scheduleCreator.createSimpleTrigger(jobInfo.getJobName(), new Date(), jobInfo.getRepeatTime(),
SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT);
}
try {
schedulerFactoryBean.getScheduler().rescheduleJob(TriggerKey.triggerKey(jobInfo.getJobName()), newTrigger);
jobInfo.setSchedulerName(schedulerFactoryBean.getScheduler().getSchedulerName());
schedulerDao.save(jobInfo);
} catch (SchedulerException e) {
log.error(e.getMessage(), e);
}
}
/**
* unscheduleJob(TriggerKey triggerKey)只是不再调度触发器,所以,当其他的触发器引用了这个Job,它们不会被改变
*
* @param jobName
* @return
*/
@Override
public boolean unScheduleJob(String jobName) {
try {
return schedulerFactoryBean.getScheduler().unscheduleJob(new TriggerKey(jobName));
} catch (SchedulerException e) {
log.error("Failed to un-schedule job - {}", jobName, e);
return false;
}
}
/**
* deleteJob(JobKey jobKey):<br>
* 1.循环遍历所有引用此Job的触发器,以取消它们的调度(to unschedule them)<br>
* 2.从jobstore中删除Job
*
* @param jobName job name
* @param jobGroup job group
* @return
*/
@Override
public boolean deleteJob(String jobName, String jobGroup) {
try {
boolean deleteJob = schedulerFactoryBean.getScheduler().deleteJob(new JobKey(jobName, jobGroup));
if (deleteJob) {
SchedulerJob job = schedulerDao.findSchedulerJobByJobName(jobName);
schedulerDao.deleteByJobName(jobName);
}
return deleteJob;
} catch (SchedulerException e) {
log.error("Failed to delete job - {}", jobName, e);
return false;
}
}
/**
* 暂停
*
* @param jobName job name
* @param jobGroup job group
* @return
*/
@Override
public boolean pauseJob(String jobName, String jobGroup) {
try {
schedulerFactoryBean.getScheduler().pauseJob(new JobKey(jobName, jobGroup));
return true;
} catch (SchedulerException e) {
log.error("Failed to pause job - {}", jobName, e);
return false;
}
}
/**
* 恢复
*
* @param jobName job name
* @param jobGroup job group
* @return
*/
@Override
public boolean resumeJob(String jobName, String jobGroup) {
try {
schedulerFactoryBean.getScheduler().resumeJob(new JobKey(jobName, jobGroup));
return true;
} catch (SchedulerException e) {
log.error("Failed to resume job - {}", jobName, e);
return false;
}
}
@Override
public boolean startJobNow(String jobName, String jobGroup) {
try {
schedulerFactoryBean.getScheduler().triggerJob(new JobKey(jobName, jobGroup));
return true;
} catch (SchedulerException e) {
log.error("Failed to start new job - {}", jobName, e);
return false;
}
}
@Transactional(readOnly = true)
@Override
public PageInfo<Map<String, Object>> findAll(Boolean cron, Integer pageNum, Integer pageSize) {
List<Map<String, Object>> list;
if (cron) {
list = schedulerDao.getJobWithCronTrigger(new RowBounds(pageNum, pageSize));
} else {
list = schedulerDao.getJobWithSimpleTrigger(new RowBounds(pageNum, pageSize));
}
return new PageInfo<>(list);
}
@Transactional(readOnly = true)
@Override
public SchedulerJob findOne(String jobName) {
return schedulerDao.findSchedulerJobByJobName(jobName);
}
}
9.Dao层
9.1.SchedulerDao
package com.littlefxc.example.quartz.dao;
import com.littlefxc.example.quartz.enitiy.SchedulerJob;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
* @author fengxuechao
* @date 12/19/2018
*/
@Repository
public interface SchedulerDao {
/**
* 仅查询simple trigger关联的Job
* 不查询cron trigger关联的job
*
* @param rowBounds 分页插件
* @return
*/
List<Map<String, Object>> getJobWithSimpleTrigger(RowBounds rowBounds);
/**
* 仅查询cron trigger关联的Job
* 不查询simple trigger关联的job
*
* @param rowBounds 分页插件
* @return
*/
List<Map<String, Object>> getJobWithCronTrigger(RowBounds rowBounds);
/**
* 根据JobName查询SchedulerJob
*
* @param jobName
* @return SchedulerJob
*/
SchedulerJob findSchedulerJobByJobName(String jobName);
List<SchedulerJob> findAll();
/**
* 保存和更新
* 当id==null时,更新
* @param jobInfo
* @return
*/
Integer save(SchedulerJob jobInfo);
Integer deleteByJobName(String jobName);
}
9.2.SchedulerDao.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.littlefxc.example.quartz.dao.SchedulerDao">
<resultMap id="SchedulerJobMap" type="SchedulerJob">
<id property="id" column="id"/>
<result property="cronExpression" column="cron_expression"/>
<result property="jobClass" column="job_class"/>
<result property="jobName" column="job_name"/>
<result property="jobGroup" column="job_group"/>
<result property="repeatTime" column="repeat_time"/>
<result property="cronJob" column="cron_job"/>
<result property="schedulerName" column="scheduler_name"/>
</resultMap>
<!-- @formatter:off -->
<sql id="sql_scheduler_job_info">cron_expression,cron_job,job_class,job_group,job_name,repeat_time, scheduler_name</sql>
<!-- @formatter:on -->
<select id="getJobWithSimpleTrigger" resultType="java.util.HashMap">
select j.JOB_NAME,
j.JOB_GROUP,
j.JOB_CLASS_NAME,
t.TRIGGER_NAME,
t.TRIGGER_GROUP,
s.REPEAT_INTERVAL,
s.TIMES_TRIGGERED
from qrtz_job_details as j
join qrtz_triggers as t
join qrtz_simple_triggers as s
ON j.JOB_NAME = t.JOB_NAME
and t.TRIGGER_NAME = s.TRIGGER_NAME
and t.TRIGGER_GROUP = s.TRIGGER_GROUP
where j.SCHED_NAME = 'scheduler-spring-mvc'
</select>
<select id="getJobWithCronTrigger" resultType="java.util.HashMap">
select j.JOB_NAME,
j.JOB_GROUP,
j.JOB_CLASS_NAME,
t.TRIGGER_NAME,
t.TRIGGER_GROUP,
c.CRON_EXPRESSION,
c.TIME_ZONE_ID
from qrtz_job_details as j
join qrtz_triggers as t
join qrtz_cron_triggers as c
ON j.JOB_NAME = t.JOB_NAME
and t.TRIGGER_NAME = c.TRIGGER_NAME
and t.TRIGGER_GROUP = c.TRIGGER_GROUP
where j.SCHED_NAME = 'scheduler-spring-mvc'
</select>
<select id="findSchedulerJobByJobName" parameterType="string" resultMap="SchedulerJobMap">
select id,
<include refid="sql_scheduler_job_info"/>
from scheduler_job_info
where job_name = #{jobName}
and scheduler_name = 'scheduler-spring-mvc'
</select>
<select id="findAll" resultMap="SchedulerJobMap">
select id,
<include refid="sql_scheduler_job_info"/>
from scheduler_job_info where scheduler_name = 'scheduler-spring-mvc'
</select>
<delete id="deleteByJobName" parameterType="string">
delete
from scheduler_job_info
where job_name = #{jobName}
and scheduler_name = 'scheduler-spring-mvc'
</delete>
<insert id="save" parameterType="SchedulerJob">
<!-- @formatter:off -->
<if test="id == null">
insert into scheduler_job_info(<include refid="sql_scheduler_job_info"/>)
values(#{cronExpression}, #{cronJob}, #{jobClass}, #{jobGroup}, #{jobName}, #{repeatTime}, 'scheduler-spring-mvc')
</if>
<!-- @formatter:on -->
<if test="id != null">
update scheduler_job_info
<set>
<if test="cronExpression!=null">cron_expression = #{cronExpression}</if>
<if test="cronJob!=null">cron_job = #{cronJob}</if>
<if test="jobClass!=null">job_class = #{jobClass}</if>
<if test="jobGroup!=null">job_group = #{jobGroup}</if>
<if test="jobName!=null">job_name = #{jobName}</if>
<if test="repeatTime!=null">repeat_time = #{repeatTime}</if>
<if test="scheduler_name!=null">scheduler_name = #{schedulerName}</if>
</set>
<where>
id = #{id}
</where>
</if>
</insert>
</mapper>
10.网页Vue+ElementUI实现
网页基本没什么变化,参考上篇文章《Spring Boot Quartz 动态任务实现方式》
11. 引用
https://blog.csdn.net/u012907049/article/details/73801122
https://blog.csdn.net/yangshangwei/article/details/78539433#withmisfirehandlinginstructiondonothing
http://blog.btmatthews.com/?p=40#comment-33797
https://www.baeldung.com/spring-quartz-schedule