王跃兴 發表於 2025-12-9 14:02:00

Quartz定时任务持久化(服务重启后自动恢复)

<h1 id="quartz-定时任务持久化重启后自动恢复">Quartz 定时任务持久化(重启后自动恢复)</h1>
<p><strong>声明: 本文内容由 ChatGPT 协助生成,仅作为个人学习与记录之用。</strong></p>
<p>Quartz 默认使用 <strong>RAMJobStore(内存存储)</strong>,服务重启后任务会丢失。<br>
要让定时任务在重启后仍然有效,必须启用:<strong>JDBCJobStore(数据库持久化)</strong></p>
<p>本文说明如何在 Spring Boot 项目中配置 Quartz 持久化,使任务存入数据库并在重启后自动恢复。</p>
<h2 id="1-启用-quartz-持久化applicationyml">1. 启用 Quartz 持久化(application.yml)</h2>
<p>示例配置:</p>
<pre><code class="language-yaml">spring:
quartz:
    job-store-type: jdbc   # 启用数据库持久化
    jdbc:
      initialize-schema: always   # 第一次启动自动建表,之后改为 never
    properties:
      org.quartz.scheduler.instanceName: QuartzScheduler
      org.quartz.scheduler.instanceId: AUTO
      org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
      org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
      org.quartz.jobStore.useProperties: false
      org.quartz.jobStore.tablePrefix: QRTZ_
      org.quartz.threadPool.threadCount: 10
</code></pre>
<p><strong>注意:</strong></p>
<ul>
<li><code>initialize-schema: always</code> 只在第一次启动时用</li>
</ul>
<p>第二次以后必须改为 <code>never</code>,避免自动重建表导致任务丢失。</p>
<ul>
<li>数据库需先创建好,Quartz 会自动建表(第一次)。</li>
</ul>
<h2 id="2-初始化数据库quartz-表结构">2. 初始化数据库(Quartz 表结构)</h2>
<p>Quartz 内置表结构 SQL,可在 <code>quartz-x.x.jar</code> 中找到:</p>
<p>路径:</p>
<p><code>org/quartz/impl/jdbcjobstore/</code></p>
<p>根据数据库选择:</p>
<table>
<thead>
<tr>
<th>数据库</th>
<th>SQL 文件</th>
</tr>
</thead>
<tbody>
<tr>
<td>MySQL</td>
<td>tables_mysql_innodb.sql</td>
</tr>
<tr>
<td>PostgreSQL</td>
<td>tables_postgres.sql</td>
</tr>
<tr>
<td>Oracle</td>
<td>tables_oracle.sql</td>
</tr>
</tbody>
</table>
<p>执行后会生成 11 张表,例如:</p>
<ul>
<li>
<p><code>QRTZ_JOB_DETAILS</code></p>
</li>
<li>
<p><code>QRTZ_TRIGGERS</code></p>
</li>
<li>
<p><code>QRTZ_CRON_TRIGGERS</code></p>
</li>
<li>
<p><code>QRTZ_SIMPLE_TRIGGERS</code></p>
</li>
<li>
<p><code>QRTZ_FIRED_TRIGGERS</code></p>
</li>
<li>
<p><code>QRTZ_SCHEDULER_STATE</code></p>
</li>
<li>
<p><code>QRTZ_LOCKS</code></p>
</li>
</ul>
<p>...</p>
<p>这些表记录 Job/Trigger,实现持久化。</p>
<h2 id="3-可选为-job-启用持久化注解存储-jobdatamap">3. 可选:为 Job 启用持久化注解(存储 JobDataMap)</h2>
<p>如果你的 Job 需要持久化任务状态,添加:</p>
<pre><code class="language-java">@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class MyJob implements Job {
    ...
}
</code></pre>
<p>功能说明:</p>
<table>
<thead>
<tr>
<th>注解</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td>@PersistJobDataAfterExecution</td>
<td>执行后的JobDataMap数据状态写回数据库</td>
</tr>
<tr>
<td>@DisallowConcurrentExecution</td>
<td>任务串行执行,避免读写冲突</td>
</tr>
</tbody>
</table>
<h3 id="disallowconcurrentexecution-的作用"><code>@DisallowConcurrentExecution</code> 的作用</h3>
<p><strong>防止同一个 Job 的多个实例并发执行。</strong></p>
<p>也就是说:</p>
<p><strong><code>Quartz 会等待当前 Job 执行完,才会执行下一次触发。</code></strong></p>
<h4 id="为什么需要这个注解">为什么需要这个注解?</h4>
<p>Quartz 默认行为是:</p>
<ul>
<li>
<p>假设你的 Job 计划每 <strong>5 秒</strong> 执行一次</p>
</li>
<li>
<p>但你的任务实际执行时间是 <strong>10 秒</strong></p>
</li>
</ul>
<p>那么:</p>
<ul>
<li>
<p>Quartz 会在第 5 秒再并发启动一个 Job 实例</p>
</li>
<li>
<p>第 10 秒再启动一个</p>
</li>
<li>
<p>这样会导致同一个 Job <strong>多实例并发执行</strong></p>
</li>
</ul>
<p>这在很多业务场景是危险的:</p>
<ul>
<li>
<p>写数据库时造成脏数据</p>
</li>
<li>
<p>写文件导致冲突</p>
</li>
<li>
<p>调接口重复提交</p>
</li>
<li>
<p>修改共享变量时出并发问题</p>
</li>
</ul>
<h4 id="加上-disallowconcurrentexecution-后">加上 @DisallowConcurrentExecution 后</h4>
<p>Quartz 保证:</p>
<p>✔ 第一个任务没执行完<br>
✔ Quartz 不会再启动第二个<br>
✔ 任务之间严格串行执行<br>
✔ 安全性强</p>
<h3 id="persistjobdataafterexecution-的作用">@PersistJobDataAfterExecution 的作用</h3>
<p><strong>让你在 Job 里面修改的参数(JobDataMap)能被保存下来,下次执行还能继续用。</strong></p>
<h3 id="举个最简单的例子">举个最简单的例子</h3>
<p>你有个定时任务,每次执行想让计数器 count +1:</p>
<pre><code class="language-java">int count = data.getInt("count");
data.put("count", count + 1);
</code></pre>
<p>如果 <strong>没有</strong> @PersistJobDataAfterExecution:</p>
<ul>
<li>
<p>每次执行 count 都从 0 开始</p>
</li>
<li>
<p>因为 Quartz 不会把你更新的值保存下来</p>
</li>
</ul>
<p>如果 <strong>加上</strong> @PersistJobDataAfterExecution:</p>
<ul>
<li>
<p>count 会变成 1、2、3、4...</p>
</li>
<li>
<p>Quartz 会把更新后的值写回数据库</p>
</li>
<li>
<p>服务重启后 count 也不会丢</p>
</li>
</ul>
<p><strong>只要你在 job 里对 JobDataMap 做写操作,想保存结果 → 就一定要加 @PersistJobDataAfterExecution + @DisallowConcurrentExecution。(防止并发造成的数据覆盖和丢失)</strong></p>
<p><strong>如果你只需要任务被保存,而不需要保存 JobDataMap,可以不加这两个注解。</strong></p>
<h2 id="4-创建-job-时必须设置-storedurably">4. 创建 Job 时必须设置 .storeDurably()</h2>
<p>持久化 Job 的关键:</p>
<pre><code class="language-java">JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
      .withIdentity("job1", "group1")
      .storeDurably()    // ★★★★★ 必须,Job 才会存入数据库
      .build();
</code></pre>
<p>不加 <code>storeDurably()</code> 的 Job 会被当成“临时 Job”,服务重启后会丢失。</p>
<p>Trigger 默认会持久化,不需要额外配置。</p>
<h2 id="5-服务重启后自动恢复机制">5. 服务重启后自动恢复机制</h2>
<p>Quartz 启动时会自动从以下表中加载任务:</p>
<ul>
<li>
<p><code>QRTZ_JOB_DETAILS</code></p>
</li>
<li>
<p><code>QRTZ_TRIGGERS</code></p>
</li>
<li>
<p><code>QRTZ_CRON_TRIGGERS</code> / <code>QRTZ_SIMPLE_TRIGGERS</code></p>
</li>
</ul>
<p>无需额外代码。</p>
<h2 id="6-如何验证持久化是否生效">6. 如何验证持久化是否生效</h2>
<ol>
<li>
<p>创建一个 Job + Trigger</p>
</li>
<li>
<p>启动服务 → 任务执行正常</p>
</li>
<li>
<p>查看数据库 QRTZ_ 前缀的表,是否有记录</p>
</li>
<li>
<p>停止服务</p>
</li>
<li>
<p>再次启动</p>
</li>
<li>
<p>任务是否自动恢复执行</p>
</li>
</ol>
<p>如能恢复,即持久化成功。</p>
<p><strong>参考文章:</strong><br>
<strong>【Quartz】(一)定时框架Quartz的持久化配置:</strong> https://blog.csdn.net/Jeffhan_java/article/details/123532049</p><br><br>
来源:https://www.cnblogs.com/foury/p/19326342
頁: [1]
查看完整版本: Quartz定时任务持久化(服务重启后自动恢复)