Tuesday, October 31, 2017

Batch Apex Examples

The following example uses a Database.QueryLocator:
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){
 
   }
 
}

You can use the following code to call the previous class.
// 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);

To exclude accounts or invoices that were deleted but are still in the Recycle Bin, include isDeleted=false in the SOQL query WHERE clause, as shown in these modified samples.
// 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);

The following class uses batch Apex to reassign all accounts owned by a specific user to a different user.
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 });
}
}

Use the following to execute the OwnerReassignment class in the previous example.
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);

The following is an example of a batch Apex class for deleting records.
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){
   }
}

This code calls the BatchDelete batch Apex class to delete old documents. The specified query selects documents to delete for all documents that are in a specified folder and that are older than a specified date. Next, the sample invokes the batch job.

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);

Using Callouts in Batch Apex

To use a callout in batch Apex, specify Database.AllowsCallouts in the class definition. For example:
global class SearchAndReplace implements Database.Batchable<sObject>,
   Database.AllowsCallouts{
}
Callouts include HTTP requests and methods defined with the webservice keyword.

Using State in Batch Apex

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 retain their values 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.

The following example summarizes a custom field total__c as the records are processed.

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){
   }
}

In addition, you can specify a variable to access the initial state of the class. You can use this variable to share the initial state with all instances of the Database.Batchable methods. For example:

// 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
     
  }
}

The initialState stores only the initial state of the class. You can’t use it to pass information between instances of the class during execution of the batch job. For example, if you change the value of initialState in execute, the second chunk of processed records can’t access the new value. Only the initial value is accessible.

Testing Batch Apex

When testing your batch Apex, you can test only one execution of the execute method. 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. When you test batch Apex, make certain that the asynchronously processed batch job is finished before testing against the results. Use the Test methods startTest and stopTest around the executeBatch method to ensure that 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. This execution order applies for Apex saved using API version 25.0 and later, but not for earlier versions.

For Apex saved using API version 22.0 and later, exceptions that occur during the execution of a batch Apex job invoked by a test method are passed to the calling test method. As a result, these exceptions cause the test method to fail. If you want to handle exceptions in the test method, enclose the code in try and catch statements. Place the catch block after the stopTest method. However, with Apex saved using Apex version 21.0 and earlier, such exceptions don’t get passed to the test method and don’t cause test methods to fail.
Note
Asynchronous calls, such as @future or executeBatch, called in a startTest, stopTest block, do not count against your limits for the number of queued jobs.

The following example tests the OwnerReassignment class.
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); 
    
   }
}

Use the System.Test.enqueueBatchJobs and System.Test.getFlexQueueOrder methods to enqueue and reorder no-operation jobs within the contexts of tests.

No comments:

Post a Comment

Lightning Inter-Component Communication Patterns

Lightning Inter-Component Communication Patterns If you’re comfortable with how a Lightning Component works and want to build producti...