spring学习笔记(26)spring整合Quartz2持久化稳健任务调度

《Quartz任务调度(3)存储与持久化操作配置详细解析 》一文中,我们通过配置quartz.properties属性文件实现了Quartz的数据库持久化操作。现在整合spring的原理,就是相当于把我们在属性文件中的配置属性整合进SchedulerFactoryBean中,来生成我们的Scheduler类。
这里需要特别注意的是,我们通过Bean配置生成的JobDetail和CronTrigger或SimpleTrigger不能被序列化,因而不能持久化到数据库中,如果想要使用持久化任务调度,我们需要编程式创建Quartz的Job等相关实现类。下面是我们的配置实例:

spring容器配置

<bean id="quartzDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
   <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://127.0.0.1:3306/quartz"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</bean>  

<!-- quartz持久化存储  -->
<bean name="quartzScheduler"
    class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="dataSource">
        <ref bean="quartzDataSource" />
    </property>
    <property name="applicationContextSchedulerContextKey" value="applicationContext" />
    <property name="quartzProperties">
    <props>
        <!-- JobStore 配置 -->
        <prop key="org.quartz.jobStore.class">org.quartz.impl.jdbcjobstore.JobStoreTX</prop>
           <!-- 数据表设置 -->
        <prop key="org.quartz.jobStore.tablePrefix">QRTZ_</prop>
        <prop key="org.quartz.jobStore.dataSource">myDatadource</prop>
    </props>
    </property>
</bean>    

在连接数据前,我们需要先在数据库中创建好对应的表格,在我开始提到的文章内有相关的sql语句。此外,关于quartzProperties还有很多配置属性,如配置线程池、集群等,在我开头提到的那篇文章内都有详细说明。

测试类配置

package tool.job;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.ObjectAlreadyExistsException;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class pickNewsJob implements Job {

    @Override
    public void execute(JobExecutionContext jec) throws JobExecutionException {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        System.out.println("在" + sdf.format(new Date()) + "更新日志");
    }

    public static void main(String args[]) throws SchedulerException {
        JobDetail jobDetail = JobBuilder.newJob(pickNewsJob.class)
                .withIdentity("job1", "jgroup1").build();
        SimpleTrigger simpleTrigger = TriggerBuilder
                .newTrigger()
                .withIdentity("trigger1")
                .withSchedule(
                        SimpleScheduleBuilder
                                .repeatSecondlyForTotalCount(10, 2)).startNow()
                .build();
        try{
            ApplicationContext ac = new ClassPathXmlApplicationContext("spring/spring-task.xml");
            Scheduler scheduler = (Scheduler) ac.getBean("quartzScheduler");
            scheduler.scheduleJob(jobDetail, simpleTrigger);
            scheduler.start();
        }catch ( ObjectAlreadyExistsException e) {
            resumeJob();
        }
    }
    /**
     *根据数据库中的记录 恢复异常中断的任务
     */
    public static void resumeJob() throws SchedulerException {
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
        // ①获取调度器中所有的触发器组
        List<String> triggerGroups = scheduler.getTriggerGroupNames();
        // ②重新恢复在tgroup1组中,名为trigger1触发器的运行
        for (int i = 0; i < triggerGroups.size(); i++) {
            List<String> triggers = scheduler.getTriggerGroupNames();
            for (int j = 0; j < triggers.size(); j++) {
                Trigger tg = scheduler.getTrigger(new TriggerKey(triggers
                        .get(j), triggerGroups.get(i)));
                // ②-1:根据名称判断
                if (tg instanceof SimpleTrigger
                        && tg.getDescription().equals("jgroup1.DEFAULT")) {//由于我们之前测试没有设置触发器所在组,所以默认为DEFAULT
                    // ②-1:恢复运行
                    scheduler.resumeJob(new JobKey(triggers.get(j),
                            triggerGroups.get(i)));
                }
            }
        }
        scheduler.start();

    }
}

在这里,如果我们测试时在运行中断后再重复运行,会出现ObjectAlreadyExistsException异常,原因是我们创建的JobDetail和Trigger等的名字和数据库中已有记录冲突,新的任务尝试持久到数据库失败。在程序中,我的处理方法是先捕捉异常,然后调用恢复任务方法,来重新执行异常中断的任务。

源码下载

本例源码可到我的github仓库https://github.com/jeanhao/spring的springQuartz文件夹下载

时间: 2024-09-13 05:42:40

spring学习笔记(26)spring整合Quartz2持久化稳健任务调度的相关文章

spring学习笔记(25)spring整合quartz多版本实现企业级任务调度

在我们的另一个专栏<深入浅出Quartz任务调度>详细的讲解了使用Quartz适用于从普通门户至网站企业级系统的任务调度实现方法.在下面我们结合实例来完整spring和quartz的整合工作,将我们对quartz的配置统一交给spring容器进行管理.quartz1与quartz2两个版本的差别较大,他们的具体差别可参考我的另一篇文章Quartz任务调度(1)概念例析快速入门.鉴于我们的实际项目中很多依旧使用着quartz1版本,下面我们会针对quartz1和quartz2的配置分别进行分析.

spring学习笔记(13)基于Schema配置AOP详解

基于Schema配置入门实例 除了基于@AspectJ注解的形式来实现AOP外,我们还可以在IOC容器中配置.先来看看一个常见的应用场景,在我们的web项目中,我们需要为service层配置事务,传统的做法是在每个业务逻辑方法重复下面配置中: Created with Raphaël 2.1.0程序开始1. 获取DAO层封装好的数据库查询API,如HIbernate中的SessionFactory/Session和mybatis中的xxxMapper2. 开启事务3. 根据入参查询数据库完成相应

spring学习笔记(16)趣谈spring 事件机制[2]:多监听器流水线式顺序处理

上一篇我们使用到的ApplicationListener是无序的,结合异步调度它能满足了我们的大部分应用场景,但现在我们来个另类的需求,我们来模拟一条作业调度流水线,它不能异步,必须按照先后次序执行不同的任务才能得到我们的最终结果. 需求示例:现在假如华中科技大学的小白想要为它的智能机器人作品申报国家创新奖,需要经过学校.省级创新科研机构.国家创新科研机构逐层审核.我们尝试通过事件来实现,核心就在监听器实现SmartApplicationListener接口.示例如下: 1. 配置事件发布者小白

spring学习笔记(10)@AspectJ研磨分析[1]入门、注解基本介绍

@AspectJ准备 AspectJ是一个面向切面的框架,它扩展了Java语言.AspectJ定义了AOP语法所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件. 在使用AspectJ之前,我们需要导入aspectJ相应的jar包,可到我的资源页http://download.csdn.net/detail/qwe6112071/9468329 中下载,而如果使用maven则可直接在pom.xml中加入如下代码: <dependency> <groupId>o

spring学习笔记(19)mysql读写分离后端AOP控制实例

在这里,我们接上一篇文章,利用JNDI访问应用服务器配置的两个数据源来模拟同时操作不同的数据库如同时操作mysql和oracle等.实际上,上个例子可能用来模拟mysql数据库主从配置读写分离更贴切些.既然如此,在本例中,我们就完成读写分离的模拟在web端的配置实例. 续上次的例子,关于JNDI数据源的配置和spring datasource的配置这里不再重复.下面着重加入AOP实现DAO层动态分库调用.可先看上篇文章<spring学习笔记(18)使用JNDI模拟访问应用服务器多数据源实例 >

spring学习笔记(21)编程式事务配置,service层概念引入

访问数据库事务导入 在我之前的文章<spring学习笔记(19)mysql读写分离后端AOP控制实例>中模拟数据库读写分离的例子,在访问数据库时使用的方法是: public <E> E add(Object object) { return (E) getSessionFactory().openSession().save(object); } 通过直接开启session而后保存对象.查询数据等操作,是没有事务的.而如果我们的项目规模变大,业务逻辑日益复杂,我们在一个方法中进行大

Spring学习笔记之依赖的注解(2)

Spring学习笔记之依赖的注解(2) 1.0 注解,不能单独存在,是Java中的一种类型 1.1 写注解 1.2 注解反射 2.0 spring的注解 spring的 @Controller@Component@Service//更多典型化注解,但是@Controller@Service建议使用 @service("personService")可以代替set get 方法,@Resource(name=personDao) @Autowired//按照类型匹配 @Qualifier

Spring学习笔记之aop动态代理(3)

Spring学习笔记之aop动态代理(3) 1.0 静态代理模式的缺点: 1.在该系统中有多少的dao就的写多少的proxy,麻烦 2.如果目标接口有方法的改动,则proxy也需要改动. PersonDao.java public interface PersonDao { public void savePerson(); } PersonDaoImpl.java public class PersonDaoImpl implements PersonDao{ public void save

Spring学习笔记2之表单数据验证、文件上传实例代码_java

在上篇文章给大家介绍了Spring学习笔记1之IOC详解尽量使用注解以及java代码,接下来本文重点给大家介绍Spring学习笔记2之表单数据验证.文件上传实例代码,具体内容,请参考本文吧! 一.表单数据验证 用户注册时,需要填写账号.密码.邮箱以及手机号,均为必填项,并且需要符合一定的格式.比如账号需要32位以内,邮箱必须符合邮箱格式,手机号必须为11位号码等.可以采用在注册时验证信息,或者专门写一个工具类用来验证:来看下在SpringMVC中如何通过简单的注释实现表单数据验证. 在javax