こんにちは、管理人の@Salesforce.Zです。
一括処理をバッチにすると、ガバナ制限を回避できるし、大量処理も可能。
うまく設計できたら、いいですね。
Apex Batchの構成
バッチのインターフェースを実装するには、3つの必要な次の 3 つのメソッドが含まれています。
- Start Method
- Execute Method
- Finish Method
それぞれの構文があります。バッチの実行流れは下記の感じです。
- Startメソッドで、処理したいデータを取得 →バッチサイズで、分割→分割分ごとにExecuteメソッドにデータチャンクを渡す
- #1で分割したチャンクごとにExecuteメソッドで処理する
- すべてのチャンクをExecuteメソッドで処理完了後にFinishメソッドが動作(送信など)
Startメソッド構文
1 |
global (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {} |
Executeメソッド 構文
1 |
global void execute(Database.BatchableContext BC, list<sObject>){} |
Finishメソッド 構文
1 |
global void finish(Database.BatchableContext BC){} |
バッチクラス例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
global with sharing class YourBatch implements Database.Batchable<sObject> { /** * Startメソッド(一回のみ実行) */ global Database.QueryLocator start(Database.BatchableContext BC) { // 対象データをクエリする(例:対象データが1,000件、バッチサイズが200、executeメソッドが5回動作) } /** * Executeメソッド(分割回数実行) */ global void execute(Database.BatchableContext BC, List<sObject> scope) { // 対象データを更新、作成、削除など } /** * Finishメソッド(一回のみ実行) */ global void finish(Database.BatchableContext BC) { // メール送信や、失敗エラー、成功エラーなどのロジック } } |
Database.Batchable インターフェースの実装
Database.QueryLocator と Iterable の比較
シンプルに対象レコードをクエリで、取得できる場合、Database.QueryLocatorでStartメソッドを用意して充分です。
複雑な場合、IterableでStartメソッドを用意する。
つまり、普段、 Database.QueryLocator でしてください。満足できない場合
Iterable を考えましょう。
# | ポイント | QueryLocator | Iterable |
1 | 返されるレコード数制限 | 5,000万まで | 50,000まで |
2 | バッチサイズ | 2,000まで | 無限 |
3 | 処理複雑さ | シンプルの場合 | 複雑な場合 |
条件付きのクエリとか | 集計関数を使うとか |
Database.QueryLocator Start メソッドの場合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
global with sharing class YourBatch implements Database.Batchable<sObject> { /** * Startメソッド(一回のみ実行) */ global Database.QueryLocator start(Database.BatchableContext BC) { // 対象データをクエリする(例:対象データが1,000件、バッチサイズが200、executeメソッドが5回動作) } /** * Executeメソッド(分割回数実行) */ global void execute(Database.BatchableContext BC, List<sObject> scope) { // 対象データを更新、作成、削除など } /** * Finishメソッド(一回のみ実行) */ global void finish(Database.BatchableContext BC) { // メール送信や、失敗エラー、成功エラーなどのロジック } } |
Iterable Startメソッドの場合
CustomIterable クラス
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
global class CustomIterable implements Iterator<Account>{ List<Account> accs {get; set;} Integer i {get; set;} // CustomIterableインスタンスの場合、このクエリ結果になる public CustomIterable(){ accs = [SELECT id, name, numberofEmployees FROM Account WHERE name = 'false']; i = 0; } global boolean hasNext(){ if(i >= accs.size()) return false; else return true; } global Account next(){ if(i == 8){ i++; return null;} i=i+1; return accs[i-1]; } } |
CustomIterable クラス を返す Iterable Startメソッド バッチクラス
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
global class YourBatchClass implements Database.batchable<Account>{ // Iterable型のStartメソッド:CustomIterable クラスの返りはAccoutなので、Iterable<Account>にするわけ global Iterable<Account> start(Database.batchableContext info){ return new CustomIterable(); } global void execute(Database.batchableContext info, List<Account> scope){ List<Account> accsToUpdate = new List<Account>(); for(Account a : scope){ a.name = 'true'; a.numberOfEmployees = 69; accsToUpdate.add(a); } update accsToUpdate; } global void finish(Database.batchableContext info){ } } |
集計関数をバッチ内で使用する Iterable Startメソッド バッチクラス 例1
バッチクラスで、そのまま集計関数をクリエしたら、下記のエラーになります。
Aggregate query does not support queryMore(), use LIMIT to restrict the results to a single batch
しかも、Iterable Startメソッドで、複雑なバッチを構築可能です。
ほとんどの場合、 Iterable Startメソッド を構築する前提となることが
イテレータ( Iterator )インターフェースとイテラブル( Iterable )インターフェースが必要になってくる
カスタムイテレータを用意した上で、カスタムイテラブルでカスタムイテレータをコールするように準備する、
具体的なアルゴリズムは下記になります。
- カスタム イテレータ(Iterator)インターフェースを実装するクラス を用意する
- カスタムイテラブル( Iterable )インターフェースを実装するクラスを用意する
- カスタムバッチインターフェースを実装するクラスを用意する
イテレータ(Iterator)とイテラブル(Iterable)およびバッチを用意してから
コール順は下記になります
- バッチで、イテラブル(Iterable)をコール
- イテラブル(Iterable)で、イテレータ(Iterator)をコールする
- イテレータ(Iterator)で、クエリを行う
IteratorとIterableを用いて、集計関数をバッチ内で使用する応用コード例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/** *Iteratorインターフェースを実装するクラス */ public class AggreIterator Implements Iterator<AggregateResult>{ public AggregateResult[] results {get;set;} public Integer index {get;set;} public AggreIterator(String query){ this.index = 0; results = Database.query(query); } public boolean hasNext(){ return results !=null && !results.isEmpty() && index < results.size(); } public AggregateResult next(){ return results[index++]; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/** *Iterabaleインターフェースを実装するクラス *@使用方法:カスタムイテレータクラス(AggreIterator)をコールする */ public class AggreIterable implements Iterable<AggregateResult>{ private String query; public AggreIterable(String soql){ this.query = soql; } public Iterator<AggregateResult> Iterator(){ return new AggreIterator(this.query); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/** *バッチクラスで、カスタムイテラブルクラス(AggreIterable)をコール */ global class SampleAggregateBatch implements Database.Batchable<AggregateResult> { // バッチ開始:Iterable Startメソッド global Iterable<AggregateResult> start(Database.BatchableContext bc){ String query = YourDao.getDataByYourContidons(); return new AggreIterable(query); } // The batch job executes and operates on one batch of records global void execute(Database.BatchableContext bc, List<sObject> scope){ for(sObject sObj : scope) { AggregateResult ar = (AggregateResult)sObj; System.debug('>>>> COUNT : ' + ar.get('cnt')); } } // The batch job finishes global void finish(Database.BatchableContext bc){ } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/** *Data Access Object:DAOクラス */ public with sharing class YourDao{ /* * クエリしたい内容 */ public static String getDataByYourContidons(){ String query = 'SELECT COUNT(Id) cnt'; query += ' , AccountId'; query += ' FROM Contact GROUP BY AccountId'; return query; } } |
ちょい解釈
Iterable Startメソッド は複雑な処理をするバッチを構築する場合に使うものなので、
イテレータとイテラブルの対象が集計関数じゃなくても良い、
クラスやなにかのコレクションのものでもよいです。
しっかり、イテレータとイテラブルを理解すれば、後は、バッチの開発の自由度がもっと高くなります。ぜひ活用できるようにしてください。
終わりに
整理
- バッチクラスはインターフェースである「Database.Batchable」を実装する
- バッチクラスはStart()、Execute()、Finish()3つメソッドを含めます。
- Start()メソッドは処理したい対象(データ、何かのコレクション)を一括集めます。
- Start()メソッドのreturnはDatabase.QueryLocator オブジェク または ジョブに渡すレコードやオブジェクトが含まれる Iterable オブジェクト
- Database.QueryLocator Startメソッドの場合、最大5,000万件レコードを集めて、処理可能。 Database.executeBatch(b, BATCH_SIZE)のバッチサイズはデフォルトは200件、最大は2,000件まで指定可能、2,000より大きい値で指定しても、2,000になる
- Iterable Startメソッドの場合、最大50,000件レコードを集めて、処理可能。 Database.executeBatch(b, BATCH_SIZE)のバッチサイズ は無限だがほかの制限が適用される
- ExecuteメソッドはStartメソッドで集めたすべてのデータを分割したchunkごとに実行される、returnはAsyncApexObjectのID
- Finishメソッドは分割したすべてのchunkが実行完了後に実行される
- Database.BatchableContextはバッチジョブを追跡用
- ジョブトランザクション全体でインスタンスメンバー変数またはデータを共有する場合は、クラス定義で Database.Stateful を使用 し、対応可能だが 使用しない場合、各トランザクションの開始時にすべてのメンバー変数が初期状態にリセット されてしまう
リファレンス
- Batch Apex error ‘Aggregate query does not support queryMore’
- Apex の一括処理の使用
- カスタムイテレータ
- sfdc バッチ 集計関数使えん? 使える方法ある
- Using an Iterator Scope definition in Batch