To use batch Apex, you must write an Apex class that implements the Salesforce-provided interface Database.Batchable, and then invoke the class programmatically.
To monitor or stop the execution of the batch Apex job, from Setup, click or .
global (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {}The start method is called at the beginning of a batch Apex job. Use the start method to collect the records or objects to be passed to the interface method execute. This method returns either a Database.QueryLocator object or an iterable that contains the records or objects being passed into the job.
Use the Database.QueryLocator object when you are using a simple query (SELECT) to generate the scope of objects used in the batch job. If you use a querylocator object, the governor limit for the total number of records retrieved by SOQL queries is bypassed. For example, a batch Apex job for the Account object can return a QueryLocator for all account records (up to 50 million records) in an organization. Another example is a sharing recalculation for the Contact object that returns a QueryLocator for all account records in an organization.
global void execute(Database.BatchableContext BC, list<P>){}
The execute method is called for each batch of records passed to the method. Use this method to do all required processing for each chunk of data.
Batches of records are not guaranteed to execute in the order they are received from the start method.
global void finish(Database.BatchableContext BC){}
The finish method is called after all batches are processed. Use this method to send confirmation emails or execute post-processing operations.
Each execution of a batch Apex job is considered a discrete transaction. For example, a batch Apex job that contains 1,000 records and is executed without the optional scope parameter from Database.executeBatch is considered five transactions of 200 records each. The Apex governor limits are reset for each transaction. If the first transaction succeeds but the second fails, the database updates made in the first transaction are not rolled back.
All of the methods in the Database.Batchable interface require a reference to a Database.BatchableContext object. Use this object to track the progress of the batch job.
| Name | Arguments | Returns | Description |
|---|---|---|---|
| getJobID | ID | Returns the ID of the AsyncApexJob object associated with this batch job as a string. Use this method to track the progress of records in the batch job. You can also use this ID with the System.abortJob method. |
The following example uses the Database.BatchableContext to query the AsyncApexJob associated with the batch job.
global void finish(Database.BatchableContext BC){ // Get the ID of the AsyncApexJob representing this batch job
// from Database.BatchableContext.
// Query the AsyncApexJob object to retrieve the current job's information. AsyncApexJob a = [SELECT Id, Status, NumberOfErrors, JobItemsProcessed, TotalJobItems, CreatedBy.Email FROM AsyncApexJob WHERE Id = :BC.getJobId()]; // Send an email to the Apex job's submitter notifying of job completion. Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage(); String[] toAddresses = new String[] {a.CreatedBy.Email}; mail.setToAddresses(toAddresses); mail.setSubject('Apex Sharing Recalculation ' + a.Status); mail.setPlainTextBody ('The batch Apex job processed ' + a.TotalJobItems + ' batches with '+ a.NumberOfErrors + ' failures.'); Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail }); }
The start method can return either a Database.QueryLocator object that contains the records to be used in the batch job or an iterable.
global class SearchAndReplace implements Database.Batchable<sObject>{ global final String Query; global final String Entity; global final String Field; global final String Value; global SearchAndReplace(String q, String e, String f, String v){ Query=q; Entity=e; Field=f;Value=v; } global Database.QueryLocator start(Database.BatchableContext BC){ return Database.getQueryLocator(query); } global void execute(Database.BatchableContext BC, List<sObject> scope){ for(sobject s : scope){ s.put(Field,Value); } update scope; } global void finish(Database.BatchableContext BC){ } }
The start method can return either a Database.QueryLocator object that contains the records to be used in the batch job, or an iterable. Use an iterable to step through the returned items more easily.
global class batchClass implements Database.batchable{ global Iterable start(Database.BatchableContext info){ return new CustomAccountIterable(); } global void execute(Database.BatchableContext info, List<Account> scope){ List<Account> accsToUpdate = new List<Account>(); for(Account a : scope){ a.Name = 'true'; a.NumberOfEmployees = 70; accsToUpdate.add(a); } update accsToUpdate; } global void finish(Database.BatchableContext info){ } }
ID batchprocessid = Database.executeBatch(reassign);
AsyncApexJob aaj = [SELECT Id, Status, JobItemsProcessed, TotalJobItems, NumberOfErrors
FROM AsyncApexJob WHERE ID =: batchprocessid ];
For more information about the AsyncApexJob object, see AsyncApexJob in the Object Reference for Salesforce and Force.com.
You can also use this ID with the System.abortJob method.
You can use the System.scheduleBatch method to schedule a batch job to run once at a future time.
The System.scheduleBatch method returns the scheduled job ID (CronTrigger ID).
String cronID = System.scheduleBatch(reassign, 'job example', 1); CronTrigger ct = [SELECT Id, TimesTriggered, NextFireTime FROM CronTrigger WHERE Id = :cronID]; // TimesTriggered should be 0 because the job hasn't started yet. System.assertEquals(0, ct.TimesTriggered); System.debug('Next fire time: ' + ct.NextFireTime); // For example:
// Next fire time: 2013-06-03 13:31:23
For more information about CronTrigger, see CronTrigger in the Object Reference for Salesforce and Force.com.
global class UpdateAccountFields implements Database.Batchable<sObject>{ global final String Query; global final String Entity; global final String Field; global final String Value; global UpdateAccountFields(String q, String e, String f, String v){ Query=q; Entity=e; Field=f;Value=v; } global Database.QueryLocator start(Database.BatchableContext BC){ return Database.getQueryLocator(query); } global void execute(Database.BatchableContext BC, List<sObject> scope){ for(Sobject s : scope){s.put(Field,Value); } update scope; } global void finish(Database.BatchableContext BC){ } }
// Query for 10 accounts String q = 'SELECT Industry FROM Account LIMIT 10'; String e = 'Account'; String f = 'Industry'; String v = 'Consulting'; Id batchInstanceId = Database.executeBatch(new UpdateAccountFields(q,e,f,v), 5);
// Query for accounts that aren't in the Recycle Bin String q = 'SELECT Industry FROM Account WHERE isDeleted=false LIMIT 10'; String e = 'Account'; String f = 'Industry'; String v = 'Consulting'; Id batchInstanceId = Database.executeBatch(new UpdateAccountFields(q,e,f,v), 5);
// Query for invoices that aren't in the Recycle Bin String q = 'SELECT Description__c FROM Invoice_Statement__c WHERE isDeleted=false LIMIT 10'; String e = 'Invoice_Statement__c'; String f = 'Description__c'; String v = 'Updated description'; Id batchInstanceId = Database.executeBatch(new UpdateInvoiceFields(q,e,f,v), 5);
global class OwnerReassignment implements Database.Batchable<sObject>{ String query; String email; Id toUserId; Id fromUserId; global Database.querylocator start(Database.BatchableContext BC){ return Database.getQueryLocator(query);} global void execute(Database.BatchableContext BC, List<sObject> scope){ List<Account> accns = new List<Account>(); for(sObject s : scope){Account a = (Account)s; if(a.OwnerId==fromUserId){ a.OwnerId=toUserId; accns.add(a); } } update accns; } global void finish(Database.BatchableContext BC){ Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage(); mail.setToAddresses(new String[] {email}); mail.setReplyTo('batch@acme.com'); mail.setSenderDisplayName('Batch Processing'); mail.setSubject('Batch Process Completed'); mail.setPlainTextBody('Batch Process has completed'); Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail }); } }
OwnerReassignment reassign = new OwnerReassignment(); reassign.query = 'SELECT Id, Name, Ownerid FROM Account ' + 'WHERE ownerid=\'' + u.id + '\''; reassign.email='admin@acme.com'; reassign.fromUserId = u; reassign.toUserId = u2; ID batchprocessid = Database.executeBatch(reassign);
global class BatchDelete implements Database.Batchable<sObject> { public String query; global Database.QueryLocator start(Database.BatchableContext BC){ return Database.getQueryLocator(query); } global void execute(Database.BatchableContext BC, List<sObject> scope){ delete scope; DataBase.emptyRecycleBin(scope); } global void finish(Database.BatchableContext BC){ } }
BatchDelete BDel = new BatchDelete(); Datetime d = Datetime.now(); d = d.addDays(-1); // Replace this value with the folder ID that contains
// the documents to delete. String folderId = '00lD000000116lD'; // Query for selecting the documents to delete BDel.query = 'SELECT Id FROM Document WHERE FolderId=\'' + folderId + '\' AND CreatedDate < '+d.format('yyyy-MM-dd')+'T'+ d.format('HH:mm')+':00.000Z'; // Invoke the batch job. ID batchprocessid = Database.executeBatch(BDel); System.debug('Returned batch process ID: ' + batchProcessId);
global class SearchAndReplace implements Database.Batchable<sObject>, Database.AllowsCallouts{ }
Callouts include HTTP requests as well as methods defined with the webService keyword.
Each execution of a batch Apex job is considered a discrete transaction. For example, a batch Apex job that contains 1,000 records and is executed without the optional scope parameter is considered five transactions of 200 records each.
If you specify Database.Stateful in the class definition, you can maintain state across these transactions. When using Database.Stateful, only instance member variables retain their values between transactions. Static member variables don’t and are reset between transactions. Maintaining state is useful for counting or summarizing records as they're processed. For example, suppose your job processed opportunity records. You could define a method in execute to aggregate totals of the opportunity amounts as they were processed.
If you don't specify Database.Stateful, all static and instance member variables are set back to their original values.
global class SummarizeAccountTotal implements Database.Batchable<sObject>, Database.Stateful{ global final String Query; global integer Summary; global SummarizeAccountTotal(String q){Query=q; Summary = 0; } global Database.QueryLocator start(Database.BatchableContext BC){ return Database.getQueryLocator(query); } global void execute( Database.BatchableContext BC, List<sObject> scope){ for(sObject s : scope){ Summary = Integer.valueOf(s.get('total__c'))+Summary; } } global void finish(Database.BatchableContext BC){ } }
// Implement the interface using a list of Account sObjects
// Note that the initialState variable is declared as final
global class MyBatchable implements Database.Batchable<sObject> { private final String initialState; String query; global MyBatchable(String intialState) { this.initialState = initialState; } global Database.QueryLocator start(Database.BatchableContext BC) { // Access initialState here
return Database.getQueryLocator(query); } global void execute(Database.BatchableContext BC, List<sObject> batch) { // Access initialState here } global void finish(Database.BatchableContext BC) { // Access initialState here } }
Note that initialState is the initial state of the class. You cannot use it to pass information between instances of the class during execution of the batch job. For example, if you changed the value of initialState in execute, the second chunk of processed records would not be able to access the new value: only the initial value would be accessible.
When testing your batch Apex, you can test only one execution of the execute method. You can use the scope parameter of the executeBatch method to limit the number of records passed into the execute method to ensure that you aren't running into governor limits.
The executeBatch method starts an asynchronous process. This means that when you test batch Apex, you must make certain that the batch job is finished before testing against the results. Use the Test methods startTest and stopTest around the executeBatch method to ensure it finishes before continuing your test. All asynchronous calls made after the startTest method are collected by the system. When stopTest is executed, all asynchronous processes are run synchronously. If you don’t include the executeBatch method within the startTest and stopTest methods, the batch job executes at the end of your test method for Apex saved using Salesforce.com API version 25.0 and later, but not in earlier versions.
public static testMethod void testBatch() { user u = [SELECT ID, UserName FROM User WHERE username='testuser1@acme.com']; user u2 = [SELECT ID, UserName FROM User WHERE username='testuser2@acme.com']; String u2id = u2.id; // Create 200 test accounts - this simulates one execute.
// Important - the Salesforce.com test framework only allows you to
// test one execute. List <Account> accns = new List<Account>(); for(integer i = 0; i<200; i++){ Account a = new Account(Name='testAccount'+'i', Ownerid = u.ID); accns.add(a); } insert accns; Test.StartTest(); OwnerReassignment reassign = new OwnerReassignment(); reassign.query='SELECT ID, Name, Ownerid ' + 'FROM Account ' + 'WHERE OwnerId=\'' + u.Id + '\'' + ' LIMIT 200'; reassign.email='admin@acme.com'; reassign.fromUserId = u.Id; reassign.toUserId = u2.Id; ID batchprocessid = Database.executeBatch(reassign); Test.StopTest(); System.AssertEquals( database.countquery('SELECT COUNT()' +' FROM Account WHERE OwnerId=\'' + u2.Id + '\''), 200); } }
Cursor limits for different Force.com features are tracked separately. For example, you can have 50 Apex query cursors, 50 batch cursors, and 50 Visualforce cursors open at the same time.