Learning Batch Class in Apex (Salesforce)

,
  • A batch class in Salesforce is a class that is used to process large amounts of data in smaller, manageable chunks, also known as batches. This allows for efficient processing of large data sets without exceeding platform limits.
  • They can be used for operations such as data migration, validation, and mass updates.
  • Batch class implement the “Database.Batchable” interface provided by Salesforce and due to this we need to implement all three methods start, execute and finish.
  • The start method is responsible for providing a query locator that retrieves the records that need to be processed by the batch job. It takes in a Database.BatchableContext object as a parameter and returns a Database.QueryLocator object.
  • The execute method is responsible for processing the records retrieved by the start method. It takes in a Database.BatchableContext object and a list of sObject records as parameters. It processes the records in the list and can also add any errors or success records to the stateful variables.
  • The finish method is responsible for performing any final actions after the execute method has finished processing all the records. It takes in a Database.BatchableContext object as a parameter and can be used to do an action, once the batch execution is finished like sending an email to the administrator with the results of the batch job, or call another batch class if required.
  • Batch classes can be executed on a schedule using the “System.scheduleBatch()” method or invoked programmatically “Database.executeBatch()” method via Apex code.
  • In Batch class, we can set the batch size which tells how many records to be processed at a time. If you don’t set a batch size its “200” by default. We can also set minimum batch size to “1” and maximum batch size to “2000”.
  • We can also use “Database.Stateful” interface in a batch class allows for maintaining state across multiple batches, tracking progress, keeping records of processed/failed records and error handling. Useful for sending an email with the results of the batch job.
  • Apex allows you to call up to 5 child batch jobs from finish method in a single transaction (batch chaining) but be mindful of limits of a single transaction (10,000 records or 10 minutes).
  • A parent batch class can call and execute a child batch class immediately after its own completion, allowing for chaining multiple batch classes together to perform complex processing.
  • Additionally, you should be aware that calling multiple batch classes from the finish method can lead to an increase in locked records during processing, which may impact application performance.
Learning Batch Class in Apex (Salesforce)

For e.g. let’s see a use case of how to create 2 batch jobs in Apex that sends birthday greetings via email to all contacts whose birthday is today and also updates BirthdayNotificationSend field on the contacts for which the email is send successfully, along with success and error records to admin.

  • The class SendBirthdayGreetingEmailBatch, sends birthday greetings via email to all contacts whose birthday is today.
  • This batch class implements the Database.Batchable and Database.Stateful interfaces. This allows the class to retrieve and process large numbers of records in batches, and to maintain state across multiple batches.
  • The class has three main methods: start, execute, and finish.
  • The start method is responsible for returning a query locator that retrieves all contacts whose birthday is today.
  • The execute method is responsible for sending the birthday greetings via email to the contacts in the current batch.
  • And the finish method is responsible for sending an email to the administrator to confirm the batch job status, and also call another batch to update the contacts , which are processed in this batch and the email is sent.
  • The class also has three private variables: exceptionMessages, contactIds, and ADMIN_EMAIL.
  • The exceptionMessages variable is used to log any errors that occur during the batch execution.
  • The contactIds variable is used to track which contacts the birthday greetings were sent to.
  • And the ADMIN_EMAIL variable is used to store the email address of the administrator who should receive the confirmation email.
/**
* SendBirthdayGreetingEmailBatch is a class that sends birthday greetings via email to all contacts whose birthday is today.
* It implements the Database.Batchable and Database.Stateful interfaces.
*/
global class SendBirthdayGreetingEmailBatch implements Database.Batchable<sObject>, Database.Stateful {
    // A list of exception messages that will be used to log any errors that occur during the batch execution
    private List<String> exceptionMessages;
    // A set of contact Ids that will be used to track which contacts the birthday greetings were sent to
    private Set<Id> contactIds;
    // The admin email address for the exception email
    private static final String ADMIN_EMAIL = 'admin@example.com';

    /**
    * Constructor method that initializes the exceptionMessages and contactIds variables
    */
    global SendBirthdayGreetingEmailBatch(){
        exceptionMessages = new List<String>();
        contactIds = new Set<Id>();
    }

    /**
    * start method is responsible for returning a query locator that retrieves all contacts whose birthday is today
    *
    * @param bc The batch context
    * @return Database.QueryLocator
    */
    global Database.QueryLocator start(Database.BatchableContext bc) {
        // Return a query locator that retrieves all contacts whose birthday is today
        return Database.getQueryLocator([SELECT Id, Email, FirstName FROM Contact WHERE Birthdate = TODAY AND Email != null]);
    }

    /**
    * execute method is responsible for sending the birthday greetings via email to the contacts in the current batch
    *
    * @param bc The batch context
    * @param scope A list of contacts in the current batch
    */
    global void execute(Database.BatchableContext bc, List<Contact> scope) {
        try{
            // Create a list to store the email messages
            List<Messaging.SingleEmailMessage> emails = new List<Messaging.SingleEmailMessage>();
            // Iterate through the contacts in the current batch
            for (Contact con : scope) {
                // Create a new email message
                Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
                // Set the recipient's email address
                email.setToAddresses(new String[] { con.Email });
                // Set the email subject
                email.setSubject('Happy Birthday, ' + con.FirstName + '!');
                // Set the email body
                email.setPlainTextBody('Wishing you a very happy birthday today! We hope it\'s a great one.');
                // Add the email message to the list
                emails.add(email);
                contactIds.add(con.Id);
            }
            // Send the emails
            Messaging.sendEmail(emails);
        } catch (Exception ex) {
            exceptionMessages.add('An exception occurred while sending birthday greeting emails: ' + ex.getMessage());
        }
    }

    /**
    * finish method is responsible for sending an email to the administrator to confirm the batch job status
    * and also call another batch if required
    *
    * @param bc The batch context
    */
    global void finish(Database.BatchableContext bc) {
        // Check if there are any exception messages
        if (!exceptionMessages.isEmpty()) {
            // Create a new SingleEmailMessage object
            Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
            // Set the recipient email address for the exception email
            email.setToAddresses(new String[] { ADMIN_EMAIL });
            // Set the email subject for exception email
            email.setSubject('Birthday Greeting Email Batch Job Completed with Errors');
            // Set the email body for exception email
            email.setPlainTextBody('The batch job to send birthday greeting emails to contacts has completed with the following errors: ' + exceptionMessages);
            // Send the exception email
            Messaging.sendEmail(new Messaging.SingleEmailMessage[] { email });
        } else {
            // Create a new SingleEmailMessage object
            Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
            // Set the recipient email address for the success email
            email.setToAddresses(new String[] { ADMIN_EMAIL });
            // Set the email subject for success email
            email.setSubject('Birthday Greeting Email Batch Job Completed Successfully');
            // Set the email body for success email
            email.setPlainTextBody('The batch job to send birthday greeting emails to contacts has completed successfully.');
            // Send the success email
            Messaging.sendEmail(new Messaging.SingleEmailMessage[] { email });
            // Check if there are any contactIds
            if(!contactIds.isEmpty()){
                // Execute the update batch
                Database.executeBatch(new UpdateBirthdayNotificationBatch(contactIds),200);
            }
        }
    }
}

  • The class UpdateBirthdayNotificationBatch, updates the BirthdayNotificationSend field of contacts to true and sends an email with success and error records after completion of the batch.
  • This batch class implements the Database.Batchable and Database.Stateful interfaces. This allows the class to retrieve and process large numbers of records in batches, and to maintain state across multiple batches.
  • The class has three main methods: start, execute, and finish.
  • The start method is responsible for returning a query locator that retrieves the contacts whose Ids are in the contactIds set.
  • The execute method is responsible for updating the BirthdayNotificationSend field to true for the contacts in the current batch and adding the success and error records to respective maps.
  • And the finish method is responsible for sending an email to the administrator with the batch job status and error and success records.
  • The class also has six private variables: contactIds, errorMap, successMap, idtosobjectMap, baseURL, and ADMIN_EMAIL.
  • The contactIds variable is used to store the set of contact ids for which the BirthdayNotificationSend field needs to be updated. This variable is being passed form t
  • The errorMap variable is used to store the contact records, which had error in updating values.
  • The successMap variable is used to store the contact records, which had a successful update.
  • The idtosobjectMap variable is used to store the contacts records by id.
  • The baseURL variable is used to store the base url for success records.
  • The ADMIN_EMAIL variable is used to store the email address of the administrator who should receive the email with success and error records.
/**
UpdateBirthdayNotificationBatch is a batch class that updates the BirthdayNotificationSend field of contacts to true
and sends an email with success and error records after completion of the batch
*/
global class UpdateBirthdayNotificationBatch implements Database.Batchable<sObject>,  Database.Stateful {
    //set to store contact ids
    private Set<Id> contactIds;
    //map to store error records
    private Map<Id, String> errorMap;
    //map to store sObject records
    private Map<Id, SObject> idtosobjectMap;
    //map to store success records
    private Map<Id, String> successMap;
    //base url for success records
    private String baseURL;
    // The admin email address for the sucess and error records mail
    private static final String ADMIN_EMAIL = 'admin@example.com';
    /*
    Constructor that initializes the class variables
    @param contactIds set of contact ids
    */
    public UpdateBirthdayNotificationBatch(Set<Id> contactIds) {
        this.contactIds = contactIds;
        errorMap = new Map<Id, String>();
        successMap = new Map<Id, String>();
        idtosobjectMap = new Map<Id, SObject>();
        baseURL = URL.getSalesforceBaseUrl().toExternalForm();
    }
    /**
    * start method is responsible for returning a query locator that retrieves the contacts whose Ids are in the contactIds set
    *
    * @param bc The batch context
    * @return Database.QueryLocator
    */
    global Database.QueryLocator start(Database.BatchableContext bc) {
        // Return a query locator that retrieves the contacts whose Ids are in the contactIds set
        return Database.getQueryLocator([SELECT Id,FirstName,LastName, BirthdayNotificationSend__c FROM Contact WHERE Id IN :contactIds]);
    }
    /**
    * execute method is responsible for updating the BirthdayNotificationSend field to true for the contacts 
    * in the current batch and adding the success and error records to respective maps
    * 
    * @param bc The batch context
    * @param scope A list of contacts in the current batch
    */
    global void execute(Database.BatchableContext bc, List<Contact> scope) {
        List<Contact> contactsToUpdate = new List<Contact>();
        // Iterate through the contacts in the current batch
        for (Contact con : scope) {
            // Update the BirthdayNotificationSend field to true
            con.BirthdayNotificationSend__c = true;
            contactsToUpdate.add(con);
        }
        if(contactsToUpdate.size() > 0) {
            List<Database.SaveResult> dsrs = Database.Update(contactsToUpdate, false);
            Integer index = 0;
            for(Database.SaveResult dsr : dsrs){
                if(!dsr.isSuccess()){
                    String errMsg = dsr.getErrors()[0].getMessage();
                    errorMap.put(contactsToUpdate[index].Id, errMsg);
                   
                } else {
                    String sucMsg = baseURL + '/' + contactsToUpdate[index].Id;
                    successMap.put(contactsToUpdate[index].Id, sucMsg);
                }
                idtosobjectMap.put(contactsToUpdate[index].Id, contactsToUpdate[index]);
                index++;
            }
        }
    }
    /**
    * finish method is responsible for sending an email to the administrator to confirm the batch job status and error and success records
    *
    * @param bc The batch context
    */
    global void finish(Database.BatchableContext bc) {
        // Check if the map is not empty before sending an email
        if(!idtosobjectMap.isEmpty()) {
            // Body of the email
            String body = 'Your batch job '
                + 'UpdateBirthdayNotificationBatch '
                + 'has finished. \n'
                + 'There were total'
                + idtosobjectMap.size()
                + ' records. Please find the attached success and error records CSV.';
            // Subject of the email
            String subject = 'Contact - Apex Batch Result for UpdateBirthdayNotificationBatch';
            // Define the email
            Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
            // Define email file attachment list
            Messaging.EmailFileAttachment[] emailAttList = new List<Messaging.EmailFileAttachment>();
            if(!errorMap.isEmpty()){
                // Creating the CSV file for error
                String finalstr = 'Id, First Name, Last Name, Error \n';
                // Set the name of the attachment
                String attName = 'ContactErrors' + system.now().format('YYYYMMDDhhmm') + '.csv';
                // Iterate through the error map and create the CSV string
                for(Id id : errorMap.keySet()){
                    string err = errorMap.get(id);
                    Contact con = (Contact) idtosobjectMap.get(id);
                    string recordString = '"'+id+'","'+con.FirstName+'","'+con.LastName+'","'+err+'"\n';
                    finalstr = finalstr +recordString;
                }
                // Create the email attachment    
                Messaging.EmailFileAttachment efa = new Messaging.EmailFileAttachment();
                efa.setFileName(attName);
                efa.setBody(Blob.valueOf(finalstr));
                emailAttList.add(efa);
            }
            if(! successMap.isEmpty()) {
                // Creating the CSV file for successful updates
                String finalstr = 'Id, Name, Link \n';
                String attName = 'ContactSuccess' + system.now().format('YYYYMMDDhhmm') + '.csv';
                for(Id id  : successMap.keySet()){
                    string suc = successMap.get(id);
                    Contact con = (Contact) idtosobjectMap.get(id);
                    string recordString = '"'+id+'","'+con.FirstName+'","'+con.LastName+'","'+suc+'"\n';
                    finalstr = finalstr +recordString;
                }
                // Create the email attachment    
                Messaging.EmailFileAttachment efa = new Messaging.EmailFileAttachment();
                efa.setFileName(attName);
                efa.setBody(Blob.valueOf(finalstr));
                emailAttList.add(efa);                 
            }
            // Sets the paramaters of the email
            email.setSubject( subject );
            email.setToAddresses(new String[] { ADMIN_EMAIL });
            email.setPlainTextBody( body );
            email.setFileAttachments(emailAttList);           
            // Sends the email
            Messaging.SendEmailResult [] r = Messaging.sendEmail(new Messaging.SingleEmailMessage[] {email});
        }
    }
}

Leave a comment