Apexバッチをfinish()から再スケジュールする方法
バッチ、スケジュール、コネクタの各クラスを組み合わせ、finish()から次回分をSystem.scheduleへ登録し直す仕組みを説明します。
なぜ自分で再スケジュールするのか
Setup>Apexクラス>Apexのスケジュールで使えるSchedule Builderは、頻度を時間単位・日単位・週単位・月単位から選ぶ形です。画面から選べる最短の頻度は1時間ごとで、それより短い間隔は選べません。
数分おきのような短い間隔で動かしたいときは、バッチのfinish()メソッドから、次の実行時刻を計算してSystem.schedule()を呼び直す方法を使います。finish()で登録するジョブは、年まで指定したcron式で1回だけ実行されるので、処理が終わるたびに次の1回分を登録し直す仕組みです。
全体の構成
| クラス | 役割 |
|---|---|
| BatchSampleSchedule | Schedulableを実装し、System.scheduleから呼ばれる入口 |
| BatchSampleConnect | IScheduler経由で呼ばれ、実際にバッチを起動するクラス |
| BatchSample | Database.Batchableを実装するバッチ本体。finish()で次回分を登録する |
| BatchSampleDao | 取引先レコードを取得するクエリを持つクラス |
| BatchSampleHelper | 取得したレコードへの更新をまとめるクラス |
BatchSampleScheduleがISchedulerという内部インターフェースを持ち、Type.forName()でクラス名からBatchSampleConnectのインスタンスを動的に作る形になっています。スケジューラ本体を直接書き換えずに、起動するクラスだけ差し替えられる作りです。
コード
BatchSampleSchedule.cls
public with sharing class BatchSampleSchedule implements Schedulable { private static final String CONNECT_CLASS = 'BatchSampleConnect'; public Interface IScheduler { void execute(SchedulableContext sc); } public void execute(SchedulableContext sc) { Type targetType = Type.forName(CONNECT_CLASS); if (targetType != null) { IScheduler connector = (IScheduler) targetType.newInstance(); connector.execute(sc); } } }
System.scheduleから呼ばれる入口です。実際の処理はクラス名を指定してBatchSampleConnectへ委ねます
BatchSampleConnect.cls
public with sharing class BatchSampleConnect implements BatchSampleSchedule.IScheduler { public void execute(SchedulableContext sc) { Database.executeBatch(new BatchSample(true), 1); } }
スケジューラとバッチ本体をつなぐクラスです。ここでバッチのスコープを指定します
BatchSample.cls
public with sharing class BatchSample implements Database.Batchable<sObject>, Database.Stateful { private BatchSampleHelper helper = new BatchSampleHelper(); private BatchSampleDao dao = new BatchSampleDao(); private Boolean scheduleNextRun = false; public BatchSample() { } public BatchSample(Boolean scheduleNextRun) { this.scheduleNextRun = scheduleNextRun; } public Database.QueryLocator start(Database.BatchableContext bc) { return this.dao.getAccounts(); } public void execute(Database.BatchableContext bc, List<Account> accounts) { accounts = this.helper.markAsProcessed(accounts); update accounts; } public void finish(Database.BatchableContext bc) { if (this.scheduleNextRun) { Datetime nextRun = Datetime.now().addMinutes(1); String jobName = 'BatchSampleJob_' + nextRun.format('yyyyMMddHHmmss'); String cronExp = nextRun.format('0 m H d M ? yyyy'); System.schedule(jobName, cronExp, new BatchSampleSchedule()); } } }
取引先を取得して更新するバッチ本体です。finish()で次回1分後の実行を登録します
BatchSampleDao.cls
public with sharing class BatchSampleDao { public Database.QueryLocator getAccounts() { return Database.getQueryLocator( 'SELECT Id FROM Account ORDER BY CreatedDate ASC' ); } }
取引先を作成日の昇順で取得するだけのクエリです
BatchSampleHelper.cls
public with sharing class BatchSampleHelper { public List<Account> markAsProcessed(List<Account> accounts) { for (Account acc : accounts) { acc.IsBatchFlag__c = true; } return accounts; } }
取得済みのリストへフィールドをセットするだけで、ループ内にDMLはありません
BatchSampleDaoのSOQLは、値を埋め込まない固定のクエリなので、1行の文字列で書いています。値をクエリに埋め込む場合は、文字列連結ではなくバインド変数かString.escapeSingleQuotes()を使ってください。
finish()のcron文字列は、Datetime.format()のパターン文字(分・時・日・月・年)を使って動的に組み立てています。年を含む7項目のcron式になりますが、値は実行のたびに計算した未来の時刻なので、日付を固定で書いているわけではありません。
手動でスケジュールを開始する
開発者コンソールを開く
実行コンソールで匿名Apexを開きます。
初回のジョブを登録する
次のコードを実行し、毎日決まった時刻に1回目を起動します。
System.schedule('BatchSampleSchedule', '0 43 23 * * ?', new BatchSampleSchedule());Apexジョブの一覧で確認する
Setupの「スケジュール済みジョブ」または「Apexジョブ」で、1分後に次のジョブが登録され続けていることを確認します。
ここで間違えやすい
| 間違い | 何が起きるか |
|---|---|
finish()で無条件に次回分をスケジュールする | 処理対象がなくなっても停止条件がないと動き続けます |
Database.Statefulを付け忘れる | finish()の時点でscheduleNextRunなどのインスタンス変数が初期値に戻ります |
Database.executeBatchのスコープを大きくしすぎる | QueryLocatorを使うときのスコープは最大2,000です。それを超える値を指定しても2,000件ずつに分割されます |
| スケジュール済み・実行中のジョブが増え続ける | 組織で同時に保持できるスケジュール済み・実行中のApexジョブは100件までです |
確認した環境
- 2026年9月 / Salesforce Summer '26(APIバージョン67.0)時点の公式ドキュメントで、System.schedule・Database.executeBatch・スケジュール済みジョブの上限を確認しています
まとめ
- Setup画面のSchedule Builderで選べる頻度は1時間ごとが最短です。それより短い間隔で回したいときは、
finish()から次回分をコードで登録し直します Database.executeBatchのスコープは、QueryLocatorを使うとき最大2,000です- 組織で同時に保持できるスケジュール済み・実行中のApexジョブは100件までです
finish()で無条件に次回をスケジュールすると、止め忘れて動き続けます。停止条件を必ず入れてください- 値を埋め込まない固定のSOQLは、文字列連結せずに1行で書きます。値を埋め込む場合はバインド変数を使います
Salesforceの導入・運用についてご相談ください
導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。