Thursday, 30 August 2012

Running a Batch Job - Apex

You can invoke a batch class from a trigger, a class, or the Developer Console. There are times when you want to run the batch job at a specified schedule. This shows you how to submit the batch class you created in Lesson 1 through the Developer Console for immediate results. You’ll also create a scheduler class that enables you to schedule the batch class.
Begin by setting up some merchandise records in the organization that don’t have any associated line items. The records that the test created in the previous don’t persist, so you‘ll create some new records to ensure the batch job has some records to process.
  1. Click the Logs tab, and then run the following in the Execute window:
    Merchandise__c[] ml = new List<Merchandise__c>();
    for (Integer i=0;i<250;i++) {
       Merchandise__c m = new Merchandise__c(
           Name='Merchandise ' + i,
           Description__c='Some description',
           Price__c=2,
           Total_Inventory__c=100);
       ml.add(m);
    }
    insert ml;
  2. Click Execute.
    This creates 250 merchandise items, which ensures that our batch class runs twice, once for the first 200 records, and once for the remaining 50 records.
  3. Let’s now submit the batch class by calling Database.executeBatch from the Developer Console. Run the following in the Execute window:
    String query = 'SELECT Id,CreatedDate FROM Merchandise__c ' + 
        'WHERE Id NOT IN (SELECT Merchandise__c FROM Line_Item__c)';
    CleanUpRecords c = new CleanUpRecords(query);
    Database.executeBatch(c);
    You’ll receive an email notification for the job’s completion. It might take a few minutes for the email to arrive. The email should state that two batches were run.
  4. To view the status of the batch job execution, click Your Name | Setup | Monitoring | Apex Jobs. Since the job finished, its status shows as completed and you can see that two batches were processed.

    Monitoring the status of a batch job 
  5. To schedule the batch job programmatically, you need to create a class that implements the Schedulable interface which invokes the batch class from its execute method. First, click Your Name | Setup | Develop | Apex Classes | New.
  6. In the code editor box, add the following class definition.
    global class MyScheduler implements Schedulable {
       
       global void execute(SchedulableContext ctx) {
            // The query used by the batch job. 
        
            String query = 'SELECT Id,CreatedDate FROM Merchandise__c ' + 
                            'WHERE Id NOT IN (SELECT Merchandise__c FROM Line_Item__c)';
                       
          CleanUpRecords c = new CleanUpRecords(query);
          Database.executeBatch(c);
       }   
    }
  7. Follow steps similar to the ones Lesson 3: Scheduling and Monitoring Scheduled Jobs to schedule the MyScheduler class.

Adding a Test for the Batch Apex Class


Prerequisites:
  • Tutorial #1: Creating Warehouse Custom Objects
  • Tutorial #8: sObjects and the Database
  • Tutorial #12: Apex Unit Tests
In this lesson, you’ll add a test class for the CleanUpRecords batch class. The test in this class invokes the batch job and verifies that it deletes all merchandise records that haven’t been purchased.
  1. In the Repository tab, click Classes in the Setup Entity Type section, and then click New.
  2. For the class name, enter TestCleanUpBatchClass and click OK.
  3. Delete the auto-generated code and add the following.
    @isTest
    private class TestCleanUpBatchClass {
    
        static testmethod void test() {
            // The query used by the batch job. 
        
            String query = 'SELECT Id,CreatedDate FROM Merchandise__c ' + 
                       'WHERE Id NOT IN (SELECT Merchandise__c FROM Line_Item__c)';
    
           // Create some test merchandise items to be deleted 
        
           //   by the batch job. 
        
           Merchandise__c[] ml = new List<Merchandise__c>();
           for (Integer i=0;i<10;i++) {
               Merchandise__c m = new Merchandise__c(
                   Name='Merchandise ' + i,
                   Description__c='Some description',
                   Price__c=2,
                   Total_Inventory__c=100);
               ml.add(m);
           }
           insert ml;
    
           Test.startTest();
           CleanUpRecords c = new CleanUpRecords(query);
           Database.executeBatch(c);
           Test.stopTest();
    
           // Verify merchandise items got deleted  
        
           Integer i = [SELECT COUNT() FROM Merchandise__c];
           System.assertEquals(i, 0);
        }
    }
  4. Click Save.

Tell Me More...

  • The test class contains one test method called test. This test method starts by constructing the query string that is to be passed to the constructor of CleanUpRecords. Since a merchandise item that hasn’t been purchased is a merchandise item that doesn’t have line items associated with it, the SOQL query specifies the following:.
    WHERE Id NOT IN (SELECT Merchandise__c FROM Line_Item__c)
    The subquery
    SELECT Merchandise__c FROM Line_Item__c
    gets the set of all merchandise items that are referenced in line items. Since the query uses the NOT IN operator in the WHERE clause, this means the merchandise items that aren’t referenced in line items are returned.
  • The test method inserts 10 merchandise items with no associated line items to be cleaned up by the batch class method. Note that the number of records inserted is less than the batch size of 200 because test methods can execute only one batch total.
  • Next, the batch class is instantiated with the query with this statement where the query variable is passed to the constructor ofCleanUpRecords.:
    CleanUpRecords c = new CleanUpRecords(query);
  • The batch class is invoked by calling Database.executeBatch and passing it the instance of the batch class:
    Database.executeBatch(c);
  • The call to Database.executeBatch is included within the Test.startTest and Test.stopTest block. This is necessary for the batch job to run in a test method. The job executes after the call to Test.stopTest. Any asynchronous code included withinTest.startTest and Test.stopTest gets executed synchronously after Test.stopTest.
  • Finally, the test verifies that all test merchandise items created in this test got deleted by checking that the count of merchandise items is zero.
  • Even though the batch class finish method sends a status email message, the email isn’t sent in this case because email messages don’t get sent from test methods.

Adding a Batch Apex Class


Prerequisites:
  • Tutorial #2: Using the Developer Console
In this lesson, you’ll create a batch Apex class that implements the Database.Batchable interface. The batch class cleans up the records that are passed in by the start method.
  1. In the Developer Console, click the Repository tab.
  2. In the Setup Entity Type section, click Classes, and then click New.
  3. For the class name, enter CleanUpRecords and click OK.
  4. Delete the auto-generated code and add the following.
    global class CleanUpRecords implements 
       Database.Batchable<sObject> {
    
       global final String query;
       
       global CleanUpRecords(String q) {
           query = q;
       }
    
       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){
           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('Record Clean Up Status: ' + a.Status);
           mail.setPlainTextBody
           ('The batch Apex job processed ' + a.TotalJobItems +
           ' batches with '+ a.NumberOfErrors + ' failures.');
           Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
       }
    }
  5. Click Save.

Tell Me More...

  • The records provided to this batch class are based on a query that is specified by the query variable. This query variable is set in the constructor of this class.
  • For each batch of records, the three methods in this class are executed starting with the start method, then the execute method, then the finish method, in this order.
  • The start provides the batch of records that the execute method will process. It returns the list of records to be processed by callingDatabase.getQueryLocator(query);. The maximum number of records that can be returned in theDatabase.QueryLocator object is 50 million.
  • The list of batch records to process is passed in the second parameter of the execute method. The execute method simply deletes the records with the delete DML statement. Since deleted records stay in the Recycle Bin for 15 days, the method also empties the Recycle Bin to delete these records permanently.
  • When a batch Apex job is invoked, a new record is added in the AsyncApexJob table that has information about the batch job, such as its status, the number of batches processed, and the total number of batches to be processed. The finish method sends an email to the job’s submitter to confirm the job completion. It performs a query on the AsyncApexJob object to get the status of the job, the submitter’s email address, and other information. It then creates a new email message and sends it using theMessaging.SingleEmailMessage methods.
In the next , you’ll add a test method that invokes this batch class.

Apex Batch Processing


Using batch Apex classes, you can process records in batches asynchronously. If you have a large number of records to process, for example, for data cleansing or archiving, batch Apex is your solution. Each invocation of a batch class results in a job being placed on the Apex job queue for execution.
The execution logic of the batch class is called once for each batch of records. The default batch size is 200 records. You can also specify a custom batch size. Furthermore, each batch execution is considered a discrete transaction. With each new batch of records, a new set of governor limits is in effect. In this way, it’s easier to ensure that your code stays within the governor execution limits. Another benefit of discrete batch transactions is to allow for partial processing of a batch of records in case one batch fails to process successfully, all other batch transactions aren’t affected and aren’t rolled back if they were processed successfully.

Batch Apex Syntax

To write a batch Apex class, your class must implement the Database.Batchable interface. Your class declaration must include the implements keyword followed by Database.Batchable<sObject>. Here is an example:
global class CleanUpRecords implements Database.Batchable<sObject> {
You must also implement three methods:
  • start method
    global (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {}
    The start method is called at the beginning of a batch Apex job. It collects the records or objects to be passed to the interface methodexecute.
  • execute method:
    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.
    This method takes the following:
    • A reference to the Database.BatchableContext object.
    • A list of sObjects, such as List<sObject>, or a list of parameterized types. If you are using a Database.QueryLocator, the returned list should be used.
    Batches of records are not guaranteed to execute in the order they are received from the start method.
  • finish 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.

Invoking a Batch Class

To invoke a batch class, instantiate it first and then call Database.executeBatch with the instance of your batch class:
BatchClass myBatchObject = new BatchClass();
Database.executeBatch(myBatchObject);
In the next steps of this tutorial, you’ll learn how to create a batch class, test it, and invoke a batch job.

Scheduling and Monitoring Scheduled Jobs - Apex


Now that you’ve seen how to create and test a schedulable class, let’s take a look at how to schedule the class using the user interface. You’ll also learn how to view the list of scheduled jobs in your organization.
  1. Click Apex Classes to go back to the Apex Classes page.
  2. Click Schedule Apex.
  3. For the job name, enter TestSchedulingApexFromTheUI.
  4. Click the lookup button next to Apex class and enter * for the search term to get a list of all classes that can be scheduled. In the search results, click MySchedulableClass.
  5. Select Weekly or Monthly for the frequency and set the frequency desired.
  6. Select the start and end dates, and a preferred start time.The schedule of a scheduled Apex job is relative to the user’s time zone.
  7. Click Save.
  8. To go to the Schedule Jobs page, click Your Name | Setup | Monitoring | Scheduled Jobs. You’ll see that your job is now listed in the job queue.
  9. Click Manage next to the job’s name.
    The page displays more details about the job, including its execution schedule.

Adding a Test for the Schedulable Class - Apex

Prerequisites:
  • Tutorial #12: Apex Unit Tests
Now that you’ve added a schedulable class, you’ll also need to add a test method to ensure that your class has test coverage. In this lesson, you’ll add a test class with one test method, which calls System.Schedule to schedule the class.
You’ll switch to the Apex Classes page to create the test class since you’ll be running the tests from there.
  1. Click Your Name | Setup | Develop | Apex Classes | New.
  2. In the code editor box, add the following test class.
    @isTest
    private class TestSchedulableClass {
    
       // CRON expression: midnight on March 15. 
        
       // Because this is a test, job executes 
        
       // immediately after Test.stopTest(). 
        
       public static String CRON_EXP = '0 0 0 15 3 ? 2022';
    
       static testmethod void test() {
          Test.startTest();
    
          // Schedule the test job 
        
          String jobId = System.schedule('ScheduleApexClassTest',
                            CRON_EXP, 
                            new MySchedulableClass());
             
          // Get the information from the CronTrigger API object 
        
          CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, 
             NextFireTime
             FROM CronTrigger WHERE id = :jobId];
    
          // Verify the expressions are the same 
        
          System.assertEquals(CRON_EXP, 
             ct.CronExpression);
    
          // Verify the job has not run 
        
          System.assertEquals(0, ct.TimesTriggered);
    
          // Verify the next time the job will run 
        
          System.assertEquals('2022-03-15 00:00:00', 
             String.valueOf(ct.NextFireTime));
          // Verify the scheduled job hasn't run yet. 
        
          Merchandise__c[] ml = [SELECT Id FROM Merchandise__c 
                                 WHERE Name = 'Scheduled Job Item'];
          System.assertEquals(ml.size(),0);
          Test.stopTest();
    
          // Now that the scheduled job has executed after Test.stopTest(), 
        
          //   fetch the new merchandise that got added. 
        
          ml = [SELECT Id FROM Merchandise__c 
                                 WHERE Name = 'Scheduled Job Item'];
          System.assertEquals(ml.size(), 1);
    
       }
    }
  3. Click Save.
  4. Click Run Test to execute the test method.

Tell Me More...

  • The test method schedules the MySchedulableClass class by calling the System.schedule method. The System.Schedulemethod takes three arguments: a name for the job, an expression used to represent the time and date the job is scheduled to run, and the name of the class. The System.schedule method uses the user's timezone for the basis of all schedules.
  • The call to System.schedule is included within the Test.startTest and Test.stopTest block. This ensures that the job gets executed after the Test.stopTest call regardless of the schedule specified in the cron expression. Any asynchronous code included within Test.startTest and Test.stopTest gets executed synchronously after Test.stopTest.
  • Finally, the test method verifies a new merchandise item got added by the scheduled class.
Tip

  • The System.Schedule method uses the user's timezone for the basis of all schedules.
  • You can only have 25 classes scheduled at one time.

Adding a Class that Implements the Schedulable Interface - Apex

Prerequisites:
  • Tutorial #1: Creating Warehouse Custom Objects
  • Tutorial #2: Using the Developer Console
  • Tutorial #8: sObjects and the Database
In this lesson, you’ll write a class that implements the Schedulable interface, which means it can be scheduled to run at a specified date and time.
  1. In the Repository tab, click Classes in the Setup Entity Type section, and then click New.
  2. For the class name, enter MySchedulableClass and click OK.
  3. Delete the auto-generated code and add the following.
    global class MySchedulableClass implements Schedulable {
       global void execute(SchedulableContext ctx) {
          CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime
                    FROM CronTrigger WHERE Id = :ctx.getTriggerId()];
    
          System.debug(ct.CronExpression);
          System.debug(ct.TimesTriggered);
    
          Merchandise__c m = new Merchandise__c(
                        Name='Scheduled Job Item',
                        Description__c='Created by the scheduler',
                        Price__c=1,
                        Total_Inventory__c=1000);
          insert m;
       }   
    }
  4. Click Save.

Tell Me More...

  • The declaration of the class contains an extra implements Schedulable at the end. This indicates that the class implements theSchedulable interface and must implement the only method that this interface contains, which is this execute method:
    global void execute(SchedulableContext sc){}
    The parameter of this method is a SchedulableContext object. It provides the getTriggerId method that returns the ID of the CronTrigger API object. After a class has been scheduled, a CronTrigger object is created that represents the scheduled job.
  • The CronTrigger object is queried to get additional information about the scheduled job. The Cron expression and the number of times the job has been run already is written to the debug log.
  • Finally, the execute method creates a merchandise record.