Update account and related contacts using Batch and Scheduled apex

Target Audience

The target audience for this article is someone who has basic knowledge about asynchronous apex in Salesforce.

Perquisite

  • Basic Knowledge about salesforce development concept
  • Asynchronous apex in salesforce

Preface

In this article I will go through:

  1. Code Snippet for the given scenario
  2. Basic Terminology of batch apex
  3. Understanding code line by line
  4. Limits in batch Apex
  5. Interview Questions related to batch apex

Learning

  1. Understanding different interfaces in batch apex
  2. Start, finish, execute method in batch apex
  3. Getting success and failed record ids using database.stateful interface
  4. Run batch apex
  5. Understand singleEmailMessage class
  6. Scheduling batch job

Scenario

Update account description, number of employees, contact last name using batch apex. Get the failure record ids in the email. Also schedule the job for every Monday.

1.Code Snippet

global class batchUpdateAccountsContacts implements Database.Batchable <sObject>,Database.Stateful,Schedulable {   
    global batchUpdateAccountsContacts(){
    }
    Set<id> successRecord = new Set<id>();
    Set<id> failRecord = new Set<id>();
   
 global Database.QueryLocator start(Database.BatchableContext info){ 
        String SOQL='Select id,name,NumberOfEmployees, description,(select id, name from contacts) from Account';
       return Database.getQueryLocator(SOQL);
   }     
   global void execute(Database.BatchableContext info, List<Account> scope){
       List<Account> accsToUpdate = new List<Account>();
       List<Contact> cUpdate = new List<Contact>();
       for(Account a : scope)
          { 
           a.description ='Test';
           a.NumberOfEmployees = 70;
           accsToUpdate.add(a); 
           for (Contact c:a.contacts){
               c.lastname = 'test+a'; 
               cUpdate.add(c);
           }
       }       
       Database.SaveResult[] srList = Database.update(accsToUpdate, false);
       Database.SaveResult[] srList1 = Database.update(cUpdate, false);


       for (Database.SaveResult sr : srList) {
            if (sr.isSuccess()) {
                // Operation was successful, so get the ID of the record that was processed
                successRecord.add(sr.getId());
            }
           
    else {
        for(Database.Error err : sr.getErrors()) {
        }
            failRecord.add(sr.getId());
    }
  }
       
        for (Database.SaveResult sr : srList1) {
            if (sr.isSuccess()) {
                successRecord.add(sr.getId());
            }
    else {
        for(Database.Error err : sr.getErrors()) {
        }
          failRecord.add(sr.getId());
    }
  }


   }     
   global void finish(Database.BatchableContext info){ 
   // 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 = :info.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('Account and contact update' + a.Status);
   mail.setPlainTextBody
       
   ('The batch Apex job processed ' + a.TotalJobItems +
   ' batches with '+ a.NumberOfErrors + ' failures.'+successRecord+'successRecordids: '+ 'failRecordids: '+ failRecord);
   Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
   } 
  
    
    global void execute(SchedulableContext SC){
        database.executeBatch(new batchUpdateAccountsContacts(),100);
       //for cron expression
        // String cronexpression = ‘0 0 0 ? * * *’
       // System.schedule(‘Testing’, cronexpression, testobj);        
    } 
}

Running batch job

  1. Go to developer console
  2. Open execute anonymous window.
  3. Run the below code
database.executeBatch(new batchUpdateAccountsContacts(),100);

Monitor status of batch job

To monitor the status of the jobs

  1. From Setup, enter Jobs in Quick Find box
  2. Select Apex Jobs
No alt text provided for this image

2.Understanding batch apex terminology

There is a governor limit on the total number of records retrieved by SOQL queries. We can only retrieve 50,000 records. But what happens if we want to retrieve more than 50,000 records. Here the batch apex comes as a savior. By using Batch apex class we can process the records in batches asynchronously. Each execution of a batch Apex job is considered a discrete transaction.

the basic structure of a batch consists of three methods:

  1. Start
  2. Execute
  3. Finish

Start method:

Use the start method to collect the records or objects to be passed to the interface method execute. This method execute only one time.

global (Database.Querylocator|Iterable(< sobject>)start(Database.BatchableContext bc){}

Execute method:

Execute method is called for each batch of records.

Batches of records are not guaranteed to execute in the order they are received from the start method.

global void execute(Database.BatchableContext BC, List<Account> scope)

Finish method:

The finish method is called after all batches are processed. Use this method to send confirmation emails or execute post-processing operations.

global void finish(Database.BatchableContext BC)

3.Understanding code line by line

Now that we have the understanding of basic terminology, its time to dig into the code.

global class batchUpdateAccountsContacts implements Database.Batchable <sObject>,Database.Stateful,Schedulable {  }

Database.Batchable <sObject>

The class (batchUpdateAccount) implements Database. Batchable interface so that class can be executed as a batch Apex job. It has start, execute and finish method.

Database.Stateful

This interface is implemented to maintain state of the job across transaction. 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.

If you don’t specify Database.Stateful, all static and instance member variables are set back to their original values.

Note: It’s important to know that using Database.Stateful will harm the batch’s performance, because the class will be serialized at the end of each execute method to update its internal state. This extra serialization results in longer execution time. There are alternative means to using Database.Stateful, such as storing data in a Custom Setting, which may offer better performance in some cases.

Schedulable Interface

This class implements Schedulable interface so that it can be scheduled to run at different intervals. It contains execute method for executing the apex job.

Start Method

Start Method below is collecting the account and related contact records to be passed to the interface method execute.

Database.QueryLocator

I am using here Database.QueryLocator. If you want to run batch on records that can be filtered by SOQL then QueryLocator is used.By using this the governor limit for the total number of records retrieved by SOQL queries is bypassed. (Default 50,000 It allow up to 50 million records).

global Database.QueryLocator start(Database.BatchableContext info){ 
        String SOQL='Select id,name,NumberOfEmployees, description,(select id, name from contacts) from Account';
       return Database.getQueryLocator(SOQL);

Iteratable

Use iteratable if records that you want to be processed by batch can not be filtered by SOQL. If you use an iterable, the governor limit for the total number of records retrieved by SOQL queries is still enforced.

Update Account and Contact Records

global void execute(Database.BatchableContext info, List<Account> scope){
       List<Account> accsToUpdate = new List<Account>();
       List<Contact> cUpdate = new List<Contact>();
       for(Account a : scope)
          { 
           a.description ='Test';
           a.NumberOfEmployees = 70;
           accsToUpdate.add(a); 
           for (Contact c:a.contacts){
               c.lastname = 'test+a'; 
               cUpdate.add(c);
           }
}

List of Accounts retrieved from start method is passed to execute method to do the processing. Here I am iterating over account and contact records and then updating the records.

I have created list(accsToUpdate) to store updated account records and (cUpdate) to store updated contact records.

I created two sets to store success records and failed records respectively.

Set<id> successRecord = new Set<id>();
Set<id> failRecord = new Set<id>();

Database.Update

Use Database.Update method instead of update with allOrNothing = false parameter I am setting false parameter in Database.Update, it means that if one record is having exception then only that record will be failed to save, but any other records in the batch saves perfectly fine.

Database.update(accsToUpdate, false);
 Database.update(cUpdate, false);

Database.SaveResult

The result of update DML operation returned by a Database method is saved in SaveResult class.

 Database.SaveResult[] srList

I iterate over the Database.SaveResult to get ID of successful and failed records.

      for (Database.SaveResult sr : srList) {
            if (sr.isSuccess()) {
                // Operation was successful, so get the ID of the record that was processed
                successRecord.add(sr.getId());
            }
           
    else {
        for(Database.Error err : sr.getErrors()) {
        }
            failRecord.add(sr.getId());
    }
  }
       
        for (Database.SaveResult sr : srList1) {
            if (sr.isSuccess()) {
                successRecord.add(sr.getId());
            }
    else {
        for(Database.Error err : sr.getErrors()) {
        }
          failRecord.add(sr.getId());
    }
  }

Finish Method

As I have earlier stated that finish method in batch apex is used for post processing logic i.e. for sending email.

  global void finish(Database.BatchableContext info){}

AsyncApexJob

Used AsyncApexJob is an object which contains various fields like status, NumberOfErrors, JobItemProcessed etc.. to get the information of current batch job

AsyncApexJob a = [SELECT Id, Status, NumberOfErrors, JobItemsProcessed,
   TotalJobItems, CreatedBy.Email FROM AsyncApexJob WHERE Id = :info.getJobId()];

Below is the code to send email once the job is completed.

SingleEmailMesssage

SingleEmailMesssage is class, Messaging is name space.

 // Send an email to the Apex job's submitter notifying of job completion.
   Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();

setToAddress(toAddress)

List of String to store email address for the email to sent and then use method setToAddress() to send email.

String[] toAddresses = new String[] {a.CreatedBy.Email};
   mail.setToAddresses(toAddresses);

setSubject(Subject)

Email subject line is sent by setSubject method

 mail.setSubject('Account and contact update' + a.Status);

setPlainTextBody(plainTextBody)

This method is used to send the text of the email. I am mailing successRecords, failure records,number of errors, totalJobitems in the email text

mail.setPlainTextBody('The batch Apex job processed ' + a.TotalJobItems +
  ' batches with '+ a.NumberOfErrors + ' failures.'+successRecord+'successRecordids: '+ 'failRecordids: '+ failRecord);
   }

sendEmail(emails, allOrNothing)

Sends the list of up to 10 emails instantiated with either SingleEmailMessage or MassEmailMessage and returns a list of SendEmailResult objects.

new Messaging.SingleEmailMessage[] { mail } is the list of email messages, they create it here on the fly

The [] means it is a list

Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail1, mail2, mail3 } );

   Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });

Email received once batch job is run

No alt text provided for this image

Schedule Apex

  1. From Setup, enter Apex Class in Quick Find box
  2. Select Schedule Apex
No alt text provided for this image

Once you select schedule apex, you will see a below screen.

No alt text provided for this image

To see all scheduled jobs

From Setup, enter Scheduled jobs in Quick Find box

No alt text provided for this image

The moment you schedule the apex class, the job gets queued.

No alt text provided for this image

Note: Till the time job is not completed, you cannot make changes to the apex class (batchUpdateAccountsContacts)

4.Limits in batch Apex

  • Up to five queued or active batch jobs are allowed for Apex
  • Maximum of 50 million records can be returned in the Database.QueryLocator object.
  • If the start method returns a QueryLocator, the optional scope parameter of Database.executeBatch can have a maximum value of 2,000. If set to a higher value, Salesforce chunks the records returned by the QueryLocator into smaller batches of up to 2,000 records.
  • If the start method returns an iterable, the scope parameter value has no upper limit; however, if you use a very high number, you may run into other limits.
  • If no size is specified with the optional scope parameter of Database.executeBatch, Salesforce chunks the records returned by the start method into batches of 200, and then passes each batch to the execute method.
  • The start, execute, and finish methods can implement up to 10 callouts each
  • The maximum number of batch executions is 250,000 per 24 hours
  • Only one batch Apex job’s start method can run at a time in an organization. Batch jobs that haven’t started yet remain in the queue until they’re started.

5.Interview Questions

What if you change the name of Execute method to Execute1 in the batch class? Will the batch job still run?

Go ahead and change Execute to Excute1 and try saving the class.

Output

Class batchUpdateAccountsContacts must implement the method: void Database.Batchable<SObject>.execute(Database.BatchableContext, List<SObject>)

Finding

It won let you save the batch class as it says class must implement execute method

Can we call a batch class from another batch class?

Yes, we can call one batch class from another batch class but only if it is called from its finish method.

Can we call a Future method from batch class?

No, we cannot directly call a future method from a batch class.

Is there is any way through which we can call future method from batch apex?

As we know that a webservice can be called from batch class and webservice can call @future method. So in your batch class call webservice and which can call your @future method. Also you can call future method from finish method in batch class.

How many can concurrent batch jobs be added to the queue?

At most, there can be 5 bath jobs queued at a time.

How can we track the status of the current running batch job?

The job Id returned by the batchable context variable helps us in finding the status of a batch through AsyncApexJob.Status.

Is it possible to do Synchronous Web service callouts from scheduled apex?

No.Synchronous Web service callouts are not supported from scheduled Apex.However, if your scheduled Apex executes a batch job, callouts are supported from the batch class.

Can we call scheduler from future method?

Yes

Can we call scheduler from future method?

Yes

Can we modify the scheduler class or classes referenced by this scheduler class if any scheduled job is pending?

No. If there are one or more active scheduled jobs for an Apex class, you cannot update the class or any classes referenced by this class through the Salesforce user interface.

Hopefully, this scenario will help in clearly understanding batch apex. For more of these kind of articles stay tuned. Happy Coding!

Reference

Using Batch Apex

Did you enjoy this article?
Signup today and receive free updates straight in your inbox.
I agree to have my personal information transfered to MailChimp ( more information )
50% LikesVS
50% Dislikes