资讯动态

SSM整合Quartz持久化实战:从任务丢失到数据库落地的完整配置指南

发布时间:2026/10/19 9:04:38 来源:尧图企业网站定制
简介这份资源面向具备一定Java Web基础的开发者聚焦SSM框架与Quartz调度器的整合实践解决定时任务配置分散、无法动态管理的问题。通过将作业与触发器信息持久化到数据库实现任务的动态增删改查适用于需要灵活调度周期任务的业务系统。压缩包共55个文件约28.2MB以40个jar依赖包为主辅以7个xml配置文件、3个properties配置、2个jsp页面及少量class与清单文件涵盖Spring、MyBatis、Quartz、Shiro等核心组件目录结构清晰便于快速搭建运行环境。目前已有706人学习下载。资源完整呈现了从Spring配置SchedulerFactoryBean、自定义Job作业类到MyBatis操作qrtz系列表、控制器暴露增删改查接口再到前端页面Ajax交互的全链路实现并附有quartz.properties等全局配置可帮助读者掌握任务持久化、线程池与并发策略调优及权限控制等关键排错思路。1. 从一次任务丢失说起SSM 整合 Quartz 持久化到底该装什么有个做企业后台的读者找我说他们系统里用 Quartz 做定时对账测试环境跑得好好的一上生产就出问题应用重启之后之前配好的定时任务全没了数据库里也查不到记录。我问他任务存哪他说存内存里RAMJobStore默认配置。这就是典型的「装是装上了但没落地」——Quartz 默认把任务、触发器、调度状态全放在 JVM 内存里进程一停全丢。要让它扛得住重启、支持动态增删改查就得把JobStore换成JobStoreTX或JobStoreCMT把任务数据持久化到数据库再通过 SSMSpring SpringMVC MyBatis把调度层和数据层串起来。这套环境在中小型后台系统里非常常见装不好就是任务重复执行、触发器丢失、集群下互相抢任务。这篇笔记按我实际搭环境的顺序走一遍先列版本和依赖再配数据源和quartz.properties然后写动态增删改查的 Service最后讲怎么验证任务真的落库了、重启后还能跑。2. 环境与版本先对齐SSM 和 Quartz 的依赖怎么选2.1 版本组合与依赖清单这套整合最怕版本打架。Quartz 2.3.x 和 Spring 4.x/5.x 都能配但 Spring 对 Quartz 的封装类在不同版本里包名有变化。我一般用下面这组稳定且资料多组件版本说明JDK1.8Quartz 2.3.x 对 JDK 8 兼容最好Spring5.3.x用spring-context-support提供SchedulerFactoryBeanSpringMVC5.3.x与 Spring 同版本MyBatis3.5.x持久层Quartz2.3.2调度核心MySQL5.7 / 8.0存 Quartz 表和业务表Druid1.2.x连接池Quartz 和业务共用Maven 依赖里最关键的是这三个!-- Quartz 核心 -- dependency groupIdorg.quartz-scheduler/groupId artifactIdquartz/artifactId version2.3.2/version /dependency !-- Spring 对 Quartz 的封装SchedulerFactoryBean 在这里 -- dependency groupIdorg.springframework/groupId artifactIdspring-context-support/artifactId version5.3.30/version /dependency !-- 事务支持JobStoreTX 需要 -- dependency groupIdorg.springframework/groupId artifactIdspring-tx/artifactId version5.3.30/version /dependency逻辑说明quartz是调度核心spring-context-support提供SchedulerFactoryBean它负责把 Spring 容器里的DataSource、TransactionManager注入给 Quartz。spring-tx不能少因为JobStoreTX自己管理事务底层要用到 Spring 的事务抽象。参数上Quartz 2.3.2 是 2.3 系列最后一个稳定版比 2.2.x 多了对DisallowConcurrentExecution的完善支持做动态任务时更省心。2.2 数据库表结构Quartz 自带的 11 张表Quartz 持久化不是随便建张表就行它有一套固定的表结构。在quartz-2.3.2.jar的org/quartz/impl/jdbcjobstore/目录下有各数据库的建表脚本MySQL 对应tables_mysql_innodb.sql。我一般直接执行这个脚本建出来 11 张表QRTZ_JOB_DETAILS任务详情存 Job 类名、分组、描述QRTZ_TRIGGERS触发器主表QRTZ_CRON_TRIGGERSCron 类型触发器QRTZ_SIMPLE_TRIGGERSSimple 类型触发器QRTZ_SCHEDULER_STATE调度器实例状态集群用QRTZ_LOCKS锁表集群同步用QRTZ_FIRED_TRIGGERS正在触发的触发器QRTZ_BLOB_TRIGGERS、QRTZ_CALENDARS、QRTZ_PAUSED_TRIGGER_GRPS辅助表执行脚本的命令mysql -uroot -p your_db tables_mysql_innodb.sql注意脚本里表名默认大写Linux 下 MySQL 默认区分大小写quartz.properties里的tablePrefix要和实际表名一致。我见过有人手动改成小写结果 Quartz 启动时报Table QRTZ_JOB_DETAILS doesnt exist排查半天。常见做法是保持脚本原样别改表名。2.3 数据源与事务管理器配置Quartz 的JobStoreTX需要自己拿数据源不能直接用 MyBatis 的SqlSessionFactory。我一般让 Quartz 和业务共用同一个 Druid 数据源但事务管理器分开配。Spring 配置里!-- 业务数据源Druid -- bean iddataSource classcom.alibaba.druid.pool.DruidDataSource init-methodinit destroy-methodclose property nameurl valuejdbc:mysql://127.0.0.1:3306/your_db?useUnicodetrueamp;characterEncodingutf8/ property nameusername valueroot/ property namepassword valueyour_pwd/ property namemaxActive value20/ /bean !-- Quartz 专用事务管理器注意不是 MyBatis 那个 -- bean idquartzTxManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean !-- SchedulerFactoryBean整合核心 -- bean idscheduler classorg.springframework.scheduling.quartz.SchedulerFactoryBean property namedataSource refdataSource/ property nametransactionManager refquartzTxManager/ property nameconfigLocation valueclasspath:quartz.properties/ property nameoverwriteExistingJobs valuetrue/ property nameautoStartup valuetrue/ /bean逻辑说明SchedulerFactoryBean是 Spring 给 Quartz 的入口dataSource告诉 Quartz 用哪个库transactionManager让JobStoreTX的事务纳入 Spring 管理。overwriteExistingJobstrue表示每次启动用配置文件里的 Job 覆盖数据库同名 Job做动态任务时建议设false否则手动加的任务可能被覆盖。autoStartuptrue让调度器随容器启动。参数上configLocation指向quartz.properties里面配jobStore.class和tablePrefix。3. quartz.properties 与 JobStore 配置持久化的关键参数3.1 从 RAMJobStore 切到 JobStoreTX默认的quartz.properties里org.quartz.jobStore.class是RAMJobStore任务存内存。要持久化改成org.quartz.scheduler.instanceName MyScheduler org.quartz.scheduler.instanceId AUTO org.quartz.threadPool.class org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount 10 org.quartz.threadPool.threadPriority 5 org.quartz.jobStore.class org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass org.quartz.impl.jdbcjobstore.StdJDBCDelegate org.quartz.jobStore.tablePrefix QRTZ_ org.quartz.jobStore.dataSource myDS org.quartz.jobStore.isClustered false org.quartz.jobStore.useProperties false org.quartz.dataSource.myDS.driver com.mysql.jdbc.Driver org.quartz.dataSource.myDS.URL jdbc:mysql://127.0.0.1:3306/your_db?useUnicodetruecharacterEncodingutf8 org.quartz.dataSource.myDS.user root org.quartz.dataSource.myDS.password your_pwd org.quartz.dataSource.myDS.maxConnections 10逻辑说明jobStore.class换成JobStoreTX是持久化的第一步它把任务、触发器、调度状态写进数据库。driverDelegateClass用StdJDBCDelegateMySQL 下也可以用MySQLDelegate但 2.3.x 里StdJDBCDelegate更通用。tablePrefix必须和建表脚本一致默认QRTZ_。isClusteredfalse单机部署如果要多节点抢任务改成true并配instanceIdAUTO。usePropertiesfalse表示 JobDataMap 里的值以序列化方式存 BLOB如果只存字符串可以设true避免序列化问题。提示org.quartz.dataSource.myDS.URL里的在 properties 文件里不用转义但在 XML 里要写成amp;。我见过有人在 properties 里也写amp;结果连不上库。3.2 用 Spring 托管数据源时怎么配上面那种写法是 Quartz 自己建连接池。如果想让 Quartz 复用 Spring 的 Druid 数据源quartz.properties里就不用配org.quartz.dataSource而是通过SchedulerFactoryBean的dataSource属性注入。这时候quartz.properties里只留org.quartz.jobStore.class org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass org.quartz.impl.jdbcjobstore.StdJDBCDelegate org.quartz.jobStore.tablePrefix QRTZ_ org.quartz.jobStore.isClustered false org.quartz.jobStore.useProperties false逻辑说明Spring 的SchedulerFactoryBean会把dataSource塞进 Quartz 的SchedulerContextQuartz 发现jobStore.dataSource没配就用注入的。这种写法连接池统一管理避免两套连接池抢连接。参数上isClustered和useProperties含义同上。我一般用这种Druid 的监控也能看到 Quartz 的连接。3.3 动态增删改查的 Service 写法持久化的目的是动态管理任务。核心是注入Scheduler用JobKey和TriggerKey操作。下面是一个可抄的 ServiceService public class QuartzJobService { Autowired private Scheduler scheduler; // 新增任务 public void addJob(String jobName, String jobGroup, String cron, Class? extends Job jobClass) throws SchedulerException { JobKey jobKey JobKey.jobKey(jobName, jobGroup); if (scheduler.checkExists(jobKey)) { throw new RuntimeException(任务已存在: jobName); } JobDetail jobDetail JobBuilder.newJob(jobClass) .withIdentity(jobKey) .storeDurably() .build(); CronTrigger trigger TriggerBuilder.newTrigger() .withIdentity(TriggerKey.triggerKey(jobName, jobGroup)) .withSchedule(CronScheduleBuilder.cronSchedule(cron)) .build(); scheduler.scheduleJob(jobDetail, trigger); } // 删除任务 public void deleteJob(String jobName, String jobGroup) throws SchedulerException { scheduler.deleteJob(JobKey.jobKey(jobName, jobGroup)); } // 修改 Cron public void updateCron(String jobName, String jobGroup, String cron) throws SchedulerException { TriggerKey triggerKey TriggerKey.triggerKey(jobName, jobGroup); CronTrigger newTrigger TriggerBuilder.newTrigger() .withIdentity(triggerKey) .withSchedule(CronScheduleBuilder.cronSchedule(cron)) .build(); scheduler.rescheduleJob(triggerKey, newTrigger); } // 暂停 / 恢复 public void pauseJob(String jobName, String jobGroup) throws SchedulerException { scheduler.pauseJob(JobKey.jobKey(jobName, jobGroup)); } public void resumeJob(String jobName, String jobGroup) throws SchedulerException { scheduler.resumeJob(JobKey.jobKey(jobName, jobGroup)); } }逻辑说明checkExists先判断任务是否存在避免重复添加。storeDurably()让 JobDetail 在没有触发器时也保留在数据库方便后续再绑触发器。scheduleJob把 JobDetail 和 Trigger 一起写库。rescheduleJob只换触发器JobDetail 不动适合改 Cron。pauseJob/resumeJob改的是触发器状态数据库里QRTZ_TRIGGERS的TRIGGER_STATE会变。参数上jobGroup用来分组管理cron是标准 Quartz Cron 表达式6 或 7 位。4. 避坑与排查整合 Quartz 持久化最容易翻车的 5 个点4.1 任务不落库重启就丢现象应用重启后之前加的任务查不到QRTZ_JOB_DETAILS表是空的。原因quartz.properties里jobStore.class还是RAMJobStore或者SchedulerFactoryBean没配dataSource。解决确认jobStore.class是JobStoreTX且SchedulerFactoryBean的dataSource指向正确库。我一般启动后直接查QRTZ_JOB_DETAILS有记录才算配通。4.2 表名大小写导致启动报错现象启动时报Table xxx.qrtz_job_details doesnt exist。原因Linux 下 MySQL 默认lower_case_table_names0区分大小写而建表脚本建的是大写QRTZ_JOB_DETAILStablePrefix配成小写就找不到。解决tablePrefix和实际表名保持一致要么都用大写要么建表时统一小写。常见做法是保持脚本原样tablePrefixQRTZ_。4.3 集群下任务重复执行现象两个节点都跑同一个任务对账数据翻倍。原因isClusteredfalse两个调度器各自独立触发。解决isClusteredtrueinstanceIdAUTO让 Quartz 通过QRTZ_LOCKS和QRTZ_SCHEDULER_STATE协调。注意集群下各节点时间要同步否则锁判断会出问题。4.4 JobDataMap 序列化失败现象加任务时报NotSerializableException。原因usePropertiesfalse时JobDataMap 里的值要序列化存 BLOB传了不可序列化的对象。解决要么让对象实现Serializable要么usePropertiestrue只存字符串。我一般传简单类型复杂参数存业务表主键Job 里再查。4.5 事务不生效任务状态和业务数据不一致现象业务数据回滚了但任务已经触发。原因Quartz 的事务和业务事务没在同一个事务管理器里。解决让SchedulerFactoryBean的transactionManager和业务用同一个DataSourceTransactionManager或者用JobStoreCMT交给容器管理。我一般单机用JobStoreTX 同一个dataSource保证连接一致。5. 装完怎么验证从数据库到调度器的完整检查5.1 三步验证任务真的持久化了第一步启动应用后查数据库SELECT JOB_NAME, JOB_GROUP, JOB_CLASS_NAME FROM QRTZ_JOB_DETAILS; SELECT TRIGGER_NAME, TRIGGER_GROUP, TRIGGER_STATE FROM QRTZ_TRIGGERS;有记录说明 JobDetail 和 Trigger 都落库了。TRIGGER_STATE是WAITING表示等待触发PAUSED表示暂停。第二步调 Service 加一个任务再查库确认新增记录。然后重启应用再查记录还在说明持久化生效。第三步看调度器是否真的触发。在 Job 实现类里打日志public class MyJob implements Job { Override public void execute(JobExecutionContext context) { System.out.println(任务触发: context.getJobDetail().getKey() 时间: new Date()); } }等 Cron 到点日志输出说明调度链路通了。如果日志没出查QRTZ_FIRED_TRIGGERS有没有记录有记录但没日志多半是 Job 类没被 Spring 管理或类名配错。5.2 一个我常犯的错有次我配完quartz.properties任务能加、能查库但就是不触发。查了半天发现SchedulerFactoryBean的autoStartup没设默认是true但被我在 XML 里手滑写成false。调度器没启动任务自然不动。从那以后我每次配完都强制走一遍查库确认落盘、重启确认不丢、等一个 Cron 周期确认触发。这三步走完才算真装好。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑