quartz1.8+spring2.3.9实现从数据库中读取定时任务

环境配置

quartz1.8和spring3.2.9。
quartz2.0以上和之下的版本差别有些不同。
我使用的是1.8版本的。

数据库建表语句

CREATE TABLE `report_tasks_manager` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `task_desc` varchar(255) DEFAULT NULL COMMENT '任务描述',
  `task_name` varchar(100) NOT NULL COMMENT '任务名称',
  `cron_expression` varchar(20) DEFAULT NULL COMMENT '任务执行表达式',
  `method_name` varchar(20) DEFAULT NULL COMMENT '要执行的方法名',
  `state` int(3) NOT NULL COMMENT '任务状态 0加入任务1取消任务',
  `con_current` int(3) DEFAULT NULL COMMENT '是否并发执行 0并发1不并发',
  `reserved1` varchar(32) DEFAULT NULL COMMENT '预留字段',
  `reserved2` varchar(32) DEFAULT NULL COMMENT '预留字段2',
  `reserved3` varchar(32) DEFAULT NULL COMMENT '预留字段3',
  `start_time` datetime DEFAULT NULL COMMENT '任务开始执行时间',
  `end_time` datetime DEFAULT NULL COMMENT '任务结束时间',
  `create_time` datetime DEFAULT NULL COMMENT '任务创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `task_name` (`task_name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;

设计思路

流程说明:
定时任务在后台开启,读取数据库中的任务信息(任务类,执行方法,执行时间等),判断定时触发器(trigger)中是否存在该任务。
  1.1 如果存在该任务,比较数据库中的任务表达式(cron)是否与当前定时器中的时间一致。
    1.1.1 如果不一致,更新当前任务时间表达式。
    1.1.2 如果一致,跳至方法3。
  1.2 如果不存在该任务,跳至方法2。
2. 如果不存在该任务,加入定时器任务中。
3.触发定时任务,执行定时任务方法。
流程图

代码详解

主要包括spring的配置文件和java类的设计。

spring配置文件

     <bean id="taskExecutor"
        class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="corePoolSize" value="1" />
        <property name="maxPoolSize" value="20" />
        <property name="queueCapacity" value="100" />
        <property name="keepAliveSeconds" value="2000" />
        <property name="rejectedExecutionHandler">
            <bean class="java.util.concurrent.ThreadPoolExecutor$AbortPolicy" />
        </property>
    </bean>

    <bean id="quartzManager" class="com.wasu.upm.report.quartz.QuartzManager">
        <property name="scheduler" ref="schedulerManager" />
    </bean>
    <bean id="quartzManagerJobDetail"
          class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="targetObject" ref="quartzManager" />
        <property name="targetMethod" value="reScheduleJob" />
        <property name="concurrent" value="false" />
    </bean>
    <bean id="cronTriggerBean" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
        <property name="jobDetail" ref="quartzManagerJobDetail" />
        <!-- 延时1秒 执行任务 -->
        <property name="startDelay" value="0"/>
        <!-- 任务执行周期 20m -->
        <property name="repeatInterval" value="20000" />
    </bean>
    <!-- 总管理类 如果将lazy-init='false'那么容器启动就会执行调度程序  -->
    <bean id="schedulerManager" lazy-init="false" autowire="no"
          class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref bean="cronTriggerBean" />
            </list>
        </property>
        <property name="taskExecutor" ref="taskExecutor"/>
    </bean>

解释说明:
taskExecutor:设置任务的并发数量。例如我们想让线程单线程执行,即taskA开始执行–>taskA执行结束;taskB开始执行–>taskB执行结束;
quartzManager:定义一个定时任务,来进行其他定时任务的管理。
quartzManagerJobDetail:具体的任务类定义执行的方法。以及该任务是否并发执行。
concurrent=false指该任务不并发执行,为true指并发执行。
cronTriggerBean:设置执行的时间。

quartzManager管理类

package com.wasu.upm.report.quartz;

import java.util.Date;
import java.util.List;

import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.CronTriggerBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;

import com.wasu.upm.commons.bo.ReportTasksManagerBo;
import com.wasu.upm.report.service.IReportManagerService;

public class QuartzManager implements BeanFactoryAware{
    private final static Logger log = LoggerFactory
            .getLogger(QuartzManager.class);
    private Scheduler scheduler;

    /**每隔1小时执行一次
    */
    private static final String DEFAULT_CRON="* * */1 * * ?";
    @Autowired
    private IReportManagerService reportManagerService; 
    private static BeanFactory beanFactory;

    public Scheduler getScheduler() {
        return scheduler;
    }

    public void setScheduler(Scheduler scheduler) {
        this.scheduler = scheduler;
    }


    /**
     * 定时要执行的方法类。
     */
    public void reScheduleJob(){
        // 1.读取数据库中的任务列表,状态为0的任务。
        Date currentDate = new Date();
        List<ReportTasksManagerBo> expireList = reportManagerService.findExpireTasks(currentDate);
        if(expireList!=null && expireList.size()>0){
            for(ReportTasksManagerBo bo : expireList){
                //配置任务列表
                removeExpireTasks(bo);
            }
        }
        List<ReportTasksManagerBo> list = reportManagerService.findTasks(currentDate);
        if(list==null||list.size()==0){
            log.warn("查询的任务列表为空");
            return;
        }
        for(ReportTasksManagerBo bo : list){
            //配置任务列表
            configSchedul(bo);
        }
    }
    /**
     * 移除过期任务
     * @param bo
     */
    private void removeExpireTasks(ReportTasksManagerBo bo) {
        try {
            CronTriggerBean trigger = (CronTriggerBean) scheduler.getTrigger(bo.getTaskName(), Scheduler.DEFAULT_GROUP);
            if(trigger!=null){
                System.out.println("==移除任务=="+bo.getTaskName());
                scheduler.pauseTrigger(trigger.getName(), trigger.getGroup());// 停止触发器
                scheduler.unscheduleJob(trigger.getName(), trigger.getGroup());// 移除触发器
                scheduler.deleteJob(trigger.getJobName(), trigger.getJobGroup());// 删除任务
            }
        } catch (SchedulerException e) {
            log.error("移除任务失败...");
            e.printStackTrace();
        }
    }

    /**
     * 配置任务列表
     * @param bo
     */
    private void configSchedul(ReportTasksManagerBo bo) {
        try {
            CronTriggerBean trigger = (CronTriggerBean) scheduler.getTrigger(bo.getTaskName(), Scheduler.DEFAULT_GROUP);
            if(trigger==null){//说明schedule中不存在该定时任务
                createTriggerTask(bo);
            }else{
                updateTriggerTask(bo,trigger);
            }
        } catch (SchedulerException e) {
            log.error("获取触发器trigger失败...");
            e.printStackTrace();
        }
    }
    /**
     * 更新任务列表
     * @param bo
     */
    private void updateTriggerTask(ReportTasksManagerBo bo,CronTriggerBean trigger) {
        if(bo.getState()==0){
            try{
                // 判断从DB中取得的任务时间和现在的quartz线程中的任务时间是否相等
                // 如果相等,则表示用户并没有重新设定数据库中的任务时间,这种情况不需要重新rescheduleJob
                if(trigger.getCronExpression()!=null&&
                        !trigger.getCronExpression().equalsIgnoreCase(bo.getCronExpression())) {
                    System.out.println("=真正更新方法:="+bo.getTaskName());
                    trigger.setCronExpression(bo.getCronExpression());
                    scheduler.rescheduleJob(bo.getTaskName(),Scheduler.DEFAULT_GROUP, trigger);
                    log.info("更新任务时间失败...");
                }
            }catch(Exception e){
                log.error("更新任务时间失败...");
                e.printStackTrace();
            }
        }else{
            this.removeExpireTasks(bo);
        }
    }
    /**
     * 创建任务列表
     * @param bo
     */
    private void createTriggerTask(ReportTasksManagerBo bo) {
        if(bo.getState()==0){
            System.out.println("=创建:="+bo.getTaskName());
            MethodInvokingJobDetailFactoryBean mjdfb = new MethodInvokingJobDetailFactoryBean();
            mjdfb.setBeanName(bo.getTaskName());
            try{
                Object obj = beanFactory.getBean(bo.getTaskName());
                mjdfb.setTargetObject(obj);
                mjdfb.setTargetMethod(bo.getMethodName());
                boolean isConcurrent=false;
                if(bo.getConCurrent()==0){
                    isConcurrent = true;
                }
                mjdfb.setConcurrent(isConcurrent);
                mjdfb.afterPropertiesSet(); //将管理Job类提交到计划管理类
                JobDetail jobDetail = new JobDetail();
                jobDetail = mjdfb.getObject();
                jobDetail.setName(bo.getTaskName());
                scheduler.addJob(jobDetail, true); // 将Job添加到管理类
                String cronExpression=null;
                if(bo.getCronExpression()==null||"".equals(bo.getCronExpression())){
                    cronExpression=DEFAULT_CRON;
                }else{
                    cronExpression = bo.getCronExpression();    
                }
                // 新一个基于Spring的时间类
                CronTriggerBean c = new CronTriggerBean();
                c.setCronExpression(cronExpression);// 设置时间表达式
                c.setName(bo.getTaskName());// 设置名称
                c.setJobDetail(jobDetail);// 注入Job
                c.setJobName(bo.getTaskName());// 设置Job名称
                scheduler.scheduleJob(c);// 注入到管理类
                scheduler.rescheduleJob(bo.getTaskName(), Scheduler.DEFAULT_GROUP,c);// 刷新管理类
            }catch(Exception e){
                log.error("创建"+bo.getTaskName()+"任务失败...");
                e.printStackTrace();
            }
        }else{
            this.removeExpireTasks(bo);
        }
    }


    public void setBeanFactory(BeanFactory factory) throws BeansException {
        this.beanFactory = factory;

    }

    public BeanFactory getBeanFactory() {
        return beanFactory;
    }   
}
<think>我们正在讨论如何在Spring框架中集成Quartz,并实现数据库动态加载定时任务及其调度时间配置。参考引用内容,我们可以总结出以下关键点:1.需要引入SpringBootStarterQuartz依赖(如引用[1]所述),它简化了QuartzSpring的集成。2.在配置文件中配置Quartz使用数据库存储(如引用[2]所示),将任务存储到数据库中,以便动态管理。3.创建任务处理类实现Quartz的Job接口(如引用[3]所示),在execute方法中执行业务逻辑,并可以通过JobDataMap获取参数。目标:从数据库加载定时任务和调度时间配置,实现动态调度。步骤:1.添加依赖2.配置Quartz使用数据库存储(JDBCJobStore)3.创建数据库表(Quartz提供的表结构)4.定义任务(Job)和触发器(Trigger)的实体,以及相应的数据库操作(存储任务和触发器配置)5.实现动态调度:从数据库读取任务和触发器配置,并注册到调度器中详细步骤:第一步:添加依赖在pom.xml中添加SpringBootStarterQuartz依赖,以及数据库驱动(如MySQL)和SpringDataJPA(如果使用)等。第二步:配置Quartz使用数据库存储在application.properties或application.yml中配置Quartz,指定使用JDBCJobStore,并配置数据源等。第三步:初始化Quartz数据库Quartz提供了一系列数据库表结构,用于存储任务、触发器等信息。需要将这些表结构创建到数据库中。第四步:创建Job类实现Quartz的Job接口,编写具体的任务执行逻辑。任务参数可以通过JobDataMap传递。第五步:动态管理任务我们需要一个服务来动态添加、更新、删除和暂停任务。这个服务将从数据库读取任务配置,并操作Quartz调度器。具体实现1.添加依赖(以Maven为例):```xml<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId></dependency><!--数据库驱动,例如MySQL--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--如果使用JPA--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>```2.配置Quartz(application.yml示例):```yamlspring:quartz:job-store-type:jdbc#使用数据库存储autoStartup:true#应用启动后自动启动调度器properties:org.quartz.scheduler.instanceName:MySchedulerorg.quartz.scheduler.instanceId:AUTOorg.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTXorg.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegateorg.quartz.jobStore.tablePrefix:QRTZ_#表前缀org.quartz.jobStore.isClustered:true#如果有多实例,可以设置为集群模式org.quartz.threadPool.threadCount:10#配置数据源,使用应用的主数据源,也可以单独配置jdbc:initialize-schema:never#如果表已存在,设置为never;第一次可以设置为always```注意:如果使用单独的数据源,需要配置`org.quartz.jobStore.dataSource`,并在Spring中定义该数据源。3.初始化数据库表:可以从Quartz的官方下载对应数据库的SQL脚本(如:http://www.quartz-scheduler.org/downloads/),然后执行。4.创建Job类:```java@ComponentpublicclassMyJobimplementsJob{@Overridepublicvoidexecute(JobExecutionContextcontext)throwsJobExecutionException{JobDataMapjobDataMap=context.getMergedJobDataMap();//从jobDataMap中获取参数Stringparameter=jobDataMap.getString("key");//执行业务逻辑//...}}```5.动态管理任务:我们需要一个服务来操作Quartz的调度器(Scheduler)。通过Scheduler可以动态添加、删除、暂停任务。```java@ServicepublicclassQuartzService{@AutowiredprivateSchedulerscheduler;//添加一个任务publicvoidaddJob(JobDetailjobDetail,Triggertrigger)throwsSchedulerException{scheduler.scheduleJob(jobDetail,trigger);}//更新任务触发器publicvoidupdateJobTrigger(TriggerKeytriggerKey,TriggernewTrigger)throwsSchedulerException{scheduler.rescheduleJob(triggerKey,newTrigger);}//删除任务publicvoiddeleteJob(JobKeyjobKey)throwsSchedulerException{scheduler.deleteJob(jobKey);}//暂停任务publicvoidpauseJob(JobKeyjobKey)throwsSchedulerException{scheduler.pauseJob(jobKey);}//恢复任务publicvoidresumeJob(JobKeyjobKey)throwsSchedulerException{scheduler.resumeJob(jobKey);}}```6.数据库加载任务配置并初始化:在应用启动时,我们需要从数据库读取所有启用的任务,并将它们注册到调度器中。```java@ComponentpublicclassQuartzInitJob{@AutowiredprivateQuartzServicequartzService;@AutowiredprivateJobRepositoryjobRepository;//假设有一个Repository从数据库查询任务配置@PostConstructpublicvoidinit(){List<JobConfig>jobConfigs=jobRepository.findAllActiveJobs();//自定义方法,查询所有需要启动的任务for(JobConfigconfig:jobConfigs){//创建JobDetailJobDetailjobDetail=JobBuilder.newJob(MyJob.class).withIdentity(config.getJobName(),config.getJobGroup()).usingJobData("key",config.getJobData())//设置参数.build();//创建TriggerTriggertrigger=TriggerBuilder.newTrigger().withIdentity(config.getTriggerName(),config.getTriggerGroup()).withSchedule(CronScheduleBuilder.cronSchedule(config.getCronExpression())).build();//将任务和触发器添加到调度器quartzService.addJob(jobDetail,trigger);}}}```注意:这里假设有一个实体类JobConfig存储了任务和触发器的配置信息,包括任务名、组名、cron表达式、参数等。关键点:-任务配置存储在数据库中,通过JPA或其他方式读取。-使用Quartz的API动态创建JobDetail和Trigger,并添加到调度器。这样,当我们需要修改任务的执行时间时,只需要更新数据库中的cron表达式,然后调用更新触发器的方法即可。同样,新增任务时,向数据库插入一条记录,然后调用添加任务的方法。注意事项:1.集群环境下,Quartz数据库存储可以保证任务不会重复执行(通过锁机制)。2.动态修改任务后,需要重新调度触发器(rescheduleJob)才能生效。通过以上步骤,我们实现了从数据库动态加载定时任务配置的功能。</think>在 Spring 框架中集成 Quartz实现数据库动态加载定时任务配置,需要以下关键步骤: ### 一、核心实现步骤 1. **添加依赖** ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <!-- 或其他数据库驱动 --> <artifactId>mysql-connector-java</artifactId> </dependency> ``` 2. **配置数据库存储** (application.yml) ```yaml spring: quartz: job-store-type: jdbc # 关键配置 autoStartup: true properties: org.quartz: jobStore: class: org.springframework.scheduling.quartz.LocalDataSourceJobStore driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate tablePrefix: QRTZ_ # Quartz表前缀 isClustered: false useProperties: true # 支持JobDataMap序列化 scheduler: instanceId: AUTO jdbc: initialize-schema: never # 生产环境建议手动初始化 ``` 3. **初始化数据库表** 使用 Quartz 官方提供的 SQL 脚本创建表(如 `tables_mysql.sql`),包含: - `QRTZ_JOB_DETAILS`(任务详情) - `QRTZ_TRIGGERS`(触发器) - `QRTZ_CRON_TRIGGERS`(Cron表达式配置) 4. **动态任务处理类** ```java @Component public class DynamicJob implements Job { @Override public void execute(JobExecutionContext context) { JobDataMap dataMap = context.getMergedJobDataMap(); String taskId = dataMap.getString("taskId"); // 根据taskId从数据库获取最新配置 ScheduledTask task = taskRepository.findById(taskId); // 执行具体业务逻辑 System.out.println("执行动态任务: " + task.getTaskName()); } } ``` 5. **任务管理服务** ```java @Service public class TaskSchedulerService { @Autowired private Scheduler scheduler; @Autowired private TaskConfigRepository repository; // 自定义DAO // 动态添加任务 public void scheduleNewTask(String taskId) throws SchedulerException { ScheduledTask task = repository.findByTaskId(taskId); JobDetail job = JobBuilder.newJob(DynamicJob.class) .withIdentity(taskId, "dynamic-group") .usingJobData("taskId", taskId) .build(); CronTrigger trigger = TriggerBuilder.newTrigger() .withIdentity(taskId + "_trigger", "dynamic-group") .withSchedule(CronScheduleBuilder.cronSchedule(task.getCronExpression())) .build(); scheduler.scheduleJob(job, trigger); } // 更新任务 public void updateTask(String taskId) throws SchedulerException { unscheduleTask(taskId); // 先删除旧任务 scheduleNewTask(taskId); // 添加新配置 } } ``` ### 二、动态配置流程 1. **配置存储** 在数据库表中存储任务配置: ```sql CREATE TABLE scheduled_tasks ( task_id VARCHAR(50) PRIMARY KEY, cron_expression VARCHAR(20) NOT NULL, task_name VARCHAR(100), enabled BOOLEAN DEFAULT true ); ``` 2. **启动时加载** 应用启动时加载所有启用的任务: ```java @PostConstruct public void initTasks() { List<ScheduledTask> tasks = repository.findAllEnabledTasks(); tasks.forEach(task -> taskSchedulerService.scheduleNewTask(task.getTaskId())); } ``` 3. **运行时更新** 通过 API 触发任务更新: ```java @RestController @RequestMapping("/tasks") public class TaskController { @PostMapping("/update/{taskId}") public ResponseEntity<?> updateTask(@PathVariable String taskId) { taskSchedulerService.updateTask(taskId); return ResponseEntity.ok("任务已更新"); } } ``` ### 三、关键注意事项 1. **集群环境** 在 `application.yml` 中启用集群配置: ```yaml org.quartz: jobStore: isClustered: true clusterCheckinInterval: 20000 ``` 2. **事务管理** 使用 `LocalDataSourceJobStore` 确保调度操作与 Spring 事务集成[^1]。 3. **配置热更新** - 修改数据库配置后调用更新接口 - 使用 `scheduler.rescheduleJob()` 更新触发器 4. **异常处理** 实现 `JobExecutionException` 处理机制,记录任务失败日志。 ### 四、验证方法 1. 直接查询 `QRTZ_TRIGGERS` 表确认触发器状态 2. 通过 Quartz API 获取注册的任务列表: ```java scheduler.getJobKeys(GroupMatcher.anyGroup()) ``` > **最佳实践**:结合 Spring Boot Actuator 暴露 `/quartz` 端点监控任务状态[^2],并通过数据库版本控制实现配置回滚。 --- ### 相关问题 1. Quartz 集群模式下如何避免任务重复执行? 2. 如何实现 Quartz 任务的失败重试机制? 3. Spring Boot 中如何监控 Quartz 调度器的健康状态? 4. 动态修改 Cron 表达式时如何保证任务不中断? 5. 数据库存储的 Quartz 任务如何迁移到新系统? [^1]: 通过 `LocalDataSourceJobStore` 实现 Spring 事务管理,确保调度操作与业务事务一致性 [^2]: Spring Boot Actuator 提供 Quartz 端点监控,需添加 `spring-boot-starter-actuator` 依赖
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值