Apexバッチの実装方法についてまとめていきます。
1.クラス構成について
Apexバッチ処理は、「スケジューラクラス」と「バッチクラス」の2クラスにて構成されています。
Apexバッチクラス
- 一括処理を行うためのクラス
- クラス構成は、start、execute、finishの3メソッドから構成されている
- startは最初に一度、finishは最後に1度呼ばれる
- executeは、startメソッドにて取得データ件数をbatchサイズで分割した回数分呼ばれる
- 実装方法は2種類「Database.QueryLocator」または「Iterable」
- 「Database.QueryLocator」
- 一般的に利用されるBatch実装方法
- 最大 5000 万件までの処理可能
- 「Iterable」
- SOQL集計クエリ、外部連携、Big Objectなどを使用する場合に使うBatch実装方法
- 最大 5 万件(ガバナ制限あり)
- 「Database.QueryLocator」
Database.QueryLocator
public class MyQueryLocatorBatch implements Database.Batchable<sobject>, Database.Stateful {
public Database.QueryLocator start(Database.BatchableContext bc) {
System.debug('*** start ***');
String query = 'Select Id, Name, Description From Account';
return Database.getQueryLocator(query);
}
public void execute(Database.BatchableContext bc, List<Account> scope) {
System.debug('*** execute ***');
List<Account> updList = new List<Account>();
for (Account acc : scope) {
Account newAcc = new Account();
newAcc.Id = acc.Id;
newAcc.Description = '更新時間' + Datetime.now();
updList.add(newAcc);
}
update updList;
}
public void finish(Database.BatchableContext bc) {
System.debug('*** finish ***');
}
}
Iterable
public with sharing class MyIterableBatch implements Database.batchable<sObject>, Database.AllowsCallouts {
public Iterable<sObject> start(Database.BatchableContext BC) {
return [SELECT name FROM Account];
}
public void execute(Database.BatchableContext BC, List<sObject> scope) {
}
public void finish(Database.BatchableContext BC) {
}
}
Apexスケジューラクラス
- バッチクラスをスケジュール設定にて定期的な呼び出しをするためのクラス
- バッチサイズを定義できる。
- スケジュール設定は以下2通り
- 「設定>Apexクラス>Apexをスケジュール」から設定(週単位/月単位、日単位/曜日単位、時間単位で設定)
- 「開発者コンソールのAnonymous Window」から設定(上記以外の細かい設定の場合)
public class MyBatchScheduler implements Schedulable {
private final Integer BATCH_SIZE = 10;
public void execute(SchedulableContext ctx) {
MyQueryLocatorBatch b = new MyQueryLocatorBatch();
Database.executeBatch(b, BATCH_SIZE);
}
}
2.クラス定義(インターフェース)について
以下のインターフェースを必要に応じて実装すべし。
- Database.Stateful:メンバ変数の値を保持する場合
- Database.AllowsCallouts:コールアウト許可
- Database.RaisesPlatformEvents:ガバナエラーのハンドリングする場合
3.エラーハンドリングについて
- エラーが発生したトランザクションのみロールバックされる。
- 他のトランザクションに影響はなく、後続処理は継続して実行される。
- エラーハンドリング方法
- try~catch:ガバナ以外のエラー用
- Database.RaisesPlatformEvents、BatchApexErrorEventの適用:ガバナエラー用
4.注意事項
- executeメソッドでのレコード実行順序の保証はない。
- 最大 5 件の一括処理ジョブを同時にキューに追加するか、有効にできます。
- 最大 100 個の Holding 一括処理ジョブを Apex Flex キュー内で保留できます。
- 24 時間での Apex 一括処理メソッドの最大実行数は、250,000または組織のユーザーライセンス数の 200 倍(組織全体のstart、execute、および finish メソッドの実行回数)
- Scope(Batchサイズ)
- Database.QueryLocatorは最大2000、Iterableは上限はないが2000以下が推奨。
- コールアウト数は100回まで
- FOR UPDATEは使用不可
5.サンプルソース
スケジューラクラス
public class MyBatchScheduler implements Schedulable {
private final Integer BATCH_SIZE = 200;
public void execute(SchedulableContext ctx) {
MyQueryLocatorBatch b = new MyQueryLocatorBatch();
Database.executeBatch(b, BATCH_SIZE);
}
}
バッチクラス
- QueryLocatorにて実装
- Database.Statefulにて変数保持
- 処理結果通知メールの送信処理
- 処理結果、開始時間、終了時間、対象データ件数、処理件数
public class MyQueryLocatorBatch implements Database.Batchable<sobject>, Database.Stateful {
Integer rowNum = 0;
Integer insRowNum = 0;
boolean status = true;
datetime startTime;
public Database.QueryLocator start(Database.BatchableContext bc) {
System.debug('*** start ***');
startTime = Datetime.now();
String query = 'Select Id, Name, Description From Account Where Name Like \'テスト%\'';
return Database.getQueryLocator(query);
}
public void execute(Database.BatchableContext bc, List<Account> scope) {
System.debug('*** execute ***');
List<Account> updList = new List<Account>();
for (Account acc : scope) {
Account newAcc = new Account();
newAcc.Id = acc.Id;
newAcc.Description = '更新時間' + Datetime.now();
updList.add(newAcc);
rowNum++;
}
try {
update updList;
insRowNum += updList.size();
} catch (Exception e) {
status = false;
}
}
public void finish(Database.BatchableContext bc) {
System.debug('*** finish ***');
sendResultMail();
}
private void sendResultMail() {
String[] toAddresses = new String[1];
toAddresses[0] = 'sample@test.com';
String subject = '処理結果通知:MyQueryLocatorBatch';
String body = '';
body += '処理結果:' + (status ? '正常' : 'エラー') + '\n';
body += '処理開始時間:' + startTime.addHours(9) + '\n';
body += '処理終了時間:' + (Datetime.now()).addHours(9) + '\n';
body += '対象データ件数:' + rowNum + '\n';
body += '処理データ件数:' + insRowNum;
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
mail.setToAddresses(toAddresses); // 送信先(String[])
mail.setSubject(subject); // 件名(String)
mail.setPlainTextBody(body); // 本文(String)
mail.setOrgWideEmailAddressId('0D2IS000000k9mjXXX'); // 送信元に設定する組織のアドレスID(設定しない場合はApexの実行者が設定される)
Messaging.sendEmail(new List<Messaging.SingleEmailMessage>{ mail });
}
}
テストクラス
@isTest
private class MyBatchSchedulerTest {
@isTest
static void testUserProcess() {
// システム管理者ユーザを新規作成する
String uniqueUserName = 'standarduser' + DateTime.now().getTime() + '@example.com';
Profile sales = [SELECT Id FROM Profile WHERE Name = 'システム管理者'];
User testUser = new User(
Alias = 'test',
Email = 'test@sample.com',
EmailEncodingKey = 'UTF-8',
LastName = 'TestUser',
LanguageLocaleKey = 'ja',
LocaleSidKey = 'ja_JP',
ProfileId = sales.Id,
TimeZoneSidKey = 'Asia/Tokyo',
UserName = uniqueUserName
);
System.runAs(testUser) {
// システム管理者ユーザで実行する
Account acc = new Account(Name = 'テスト');
insert acc;
// バッチクラス実行
MyBatchScheduler scheTest = new MyBatchScheduler();
scheTest.execute(null);
}
}
}
。
6. まとめ
- バッチクラスはQueryLocatorにて実装すべき。
- エラーハンドリングは
- 基本は「Apexエラー通知設定」+「try~Catch」+「処理結果メール通知」を実装すべき。
- 大規模プロジェクトであれば、Database.RaisesPlatformEvents、BatchApexErrorEventを追加実装した方がいいかも。
コメント