【Salesforce】Apexバッチの実装方法について

未分類

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

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.エラーハンドリングについて

  • エラーが発生したトランザクションのみロールバックされる。
  • 他のトランザクションに影響はなく、後続処理は継続して実行される。
  • エラーハンドリング方法
    1. try~catch:ガバナ以外のエラー用
    2. 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は使用不可

参考:Apex 開発者ガイド「Apex の一括処理の制限」

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を追加実装した方がいいかも。

コメント

タイトルとURLをコピーしました