1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Part 9: バッチ処理におけるエラーハンドリング

Posted at

Part 9: バッチ処理 (Batch Apex) におけるエラーハンドリング

前回は Apex トリガーのエラーハンドリング を解説しました。
今回は バッチ処理 (Batch Apex) におけるエラーハンドリング について解説します。


1. バッチ処理におけるエラーハンドリングの考え方

1.1. バッチ処理の特徴

Apex バッチ (Database.Batchable) は、大量データの処理 に適しています。
しかし、一部のレコードでエラーが発生すると、すべてのレコードがロールバックされるわけではない ため、
エラーハンドリングが重要 になります。

バッチ処理の流れ:

  1. start() メソッドで処理対象のレコードを取得
  2. execute() メソッドでバッチごとに処理を実行
  3. finish() メソッドで完了処理(通知やログ記録など)

2. execute() メソッド内のエラーハンドリング

2.1. try-catch でエラーをキャッチする

✅ OK 例: try-catch で個々のレコードのエラーを処理

global class AccountBatchJob implements Database.Batchable<sObject> {
    global Database.QueryLocator start(Database.BatchableContext BC) {
        return Database.getQueryLocator('SELECT Id, Name FROM Account');
    }

    global void execute(Database.BatchableContext BC, List<Account> scope) {
        List<Account> updateList = new List<Account>();
        for (Account acc : scope) {
            try {
                if (String.isEmpty(acc.Name)) {
                    throw new CustomBatchException('アカウント名が空です: ' + acc.Id);
                }
                acc.Name = acc.Name.toUpperCase();
                updateList.add(acc);
            } catch (CustomBatchException e) {
                ErrorLogService.logError(e, 'AccountBatchJob', 'execute');
            }
        }

        if (!updateList.isEmpty()) {
            update updateList;
        }
    }

    global void finish(Database.BatchableContext BC) {
        System.debug('バッチ処理完了');
    }
}

このコードでは:

  • try-catchエラーのあるレコードだけスキップし、正常なレコードを処理
  • ErrorLogService.logError() を使って エラーログを記録

適用ケース:
一部のエラーは許容しつつ、可能な限り処理を続けたい場合


3. Database.Stateful を活用してエラーリストを蓄積

バッチ処理では execute() の処理が独立している ため、
Database.Stateful を使って エラーリストを蓄積し、最終的にログを出力 する方法もあります。

✅ OK 例: Database.Stateful を利用したエラーハンドリング

global class AccountBatchJob implements Database.Batchable<sObject>, Database.Stateful {
    private List<String> errorMessages = new List<String>();

    global Database.QueryLocator start(Database.BatchableContext BC) {
        return Database.getQueryLocator('SELECT Id, Name FROM Account');
    }

    global void execute(Database.BatchableContext BC, List<Account> scope) {
        for (Account acc : scope) {
            try {
                if (String.isEmpty(acc.Name)) {
                    throw new CustomBatchException('アカウント名が空です: ' + acc.Id);
                }
                acc.Name = acc.Name.toUpperCase();
            } catch (CustomBatchException e) {
                errorMessages.add(e.getMessage());
            }
        }
    }

    global void finish(Database.BatchableContext BC) {
        if (!errorMessages.isEmpty()) {
            System.debug('エラーリスト: ' + JSON.serialize(errorMessages));
            ErrorLogService.saveErrors(errorMessages); // エラーを保存
        }
    }
}

このコードでは:

  • Database.Stateful を使用して、エラーメッセージを finish() まで保持
  • finish() メソッドで エラーログを一括保存

適用ケース:
エラー情報をまとめてログ出力したい場合


4. Database.Savepoint でトランザクションを制御する

バッチ処理の トランザクション単位でロールバックする 方法として、Database.Savepoint を活用できます。

✅ OK 例: Savepoint を活用したリカバリ

global class AccountBatchJob implements Database.Batchable<sObject> {
    global Database.QueryLocator start(Database.BatchableContext BC) {
        return Database.getQueryLocator('SELECT Id, Name FROM Account');
    }

    global void execute(Database.BatchableContext BC, List<Account> scope) {
        Database.Savepoint sp = Database.setSavepoint();
        try {
            update scope; // 一括更新
        } catch (DmlException e) {
            Database.rollback(sp); // エラー時にロールバック
            for (Account acc : scope) {
                try {
                    update acc; // 1 レコードずつ更新
                } catch (DmlException ex) {
                    ErrorLogService.logError(ex, 'AccountBatchJob', 'execute');
                }
            }
        }
    }

    global void finish(Database.BatchableContext BC) {
        System.debug('バッチ処理完了');
    }
}

このコードでは:

  • Database.Savepointスコープ単位の更新を試行
  • 失敗した場合はロールバックし、1 件ずつリトライ

適用ケース:
一括更新時にエラーが発生しても、可能な限り処理を続けたい場合


5. エラー通知を finish() で送信

バッチ処理でエラーが発生した場合、管理者にメール通知 するのも有効です。

✅ OK 例: Messaging.SingleEmailMessage でエラーメール通知

global void finish(Database.BatchableContext BC) {
    if (!errorMessages.isEmpty()) {
        String body = 'バッチ処理でエラーが発生しました。\n' + String.join(errorMessages, '\n');
        
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        mail.setToAddresses(new String[] {'admin@example.com'});
        mail.setSubject('【警告】バッチ処理のエラー');
        mail.setPlainTextBody(body);
        Messaging.sendEmail(new Messaging.SingleEmailMessage[] {mail});
    }
}

このコードでは:

  • Messaging.SingleEmailMessage を使って エラーリストをメールで通知

適用ケース:
エラー発生時に管理者へ即時通知したい場合


まとめ

今回は Apex バッチ処理のエラーハンドリング を解説しました。

  • try-catch で個々のレコードのエラーを処理
  • Database.Stateful でエラーリストを蓄積し、finish() で一括保存
  • Savepoint を活用して一括更新のリカバリを行う
  • エラー通知を Messaging.SingleEmailMessage で送信

次の Part 10 では、フューチャーメソッド (Future Methods) や Queueable Apex におけるエラーハンドリング を解説します!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?