Stat Tracker

Thursday, December 23, 2010

Leveraging Scheduled Apex to link Batch Apex Jobs

When I have a batch Apex job finish, how do I execute a second batch Apex job? Its a question that I see frequently in the developer boards at developer.force.com and for good reason. Many enterprise developers who have moved over to Force.com have worked with batch schedulers in some form or another (CNTRL-M, Mainframe JCL, ETC).

But batch Apex does not allow you to directly invoke batch Apex to start another job due to governor limits. How can we ensure that a second batch process only executes after the first batch process completes successfully? There is more than one way to solve this problem. For this case I am going to show you how to resolve this using Apex scheduling. The pattern looks like the following:
  1. Create batch Apex 1.
  2. Create batch Apex 2.
  3. Create schedulable Apex.
  4. Inside finish() method of batch Apex 1,  invoke the schedule Apex with an execution time of now().
  5. Inside the schedule Apex, execute batch Apex 2.
This pattern will allow you to execute batches sequentially, and only after successful completion of the first batch. The pattern can be repeated to chain multiple jobs together.

Now to share some quick sample code. This code below doesn't do anything valuable other than show this pattern.

BATCH APEX 1 - The first batch Apex process.


global class AccountBatch1 implements Database.Batchable<sobject>
{
   global final String Query;
   global final String Entity;
   global final String Field;
   global final String Value;


   global AccountBatch1(String q)
   {
             Query=q;
   }
   global Database.QueryLocator start(Database.BatchableContext BC)
   {
      return Database.getQueryLocator(query);
   }
   global void execute(Database.BatchableContext BC,
                       List<sObject> scope)
   {
         List<Account> updateAccts = new List<Account>();
      for(Sobject s : scope)
      {
          Account a = (Account) s;
          a.Name = a.Name + 'Batch 1.';
      }     
      update updateAccts;
   }
   //The batch process has completed successfully. Schedule next batch.   
   global void finish(Database.BatchableContext BC)
   {
        System.debug(LoggingLevel.WARN,'Batch Process 1 Finished');
        //Build the system time of now + 20 seconds to schedule the batch apex.
        Datetime sysTime = System.now();
        sysTime = sysTime.addSeconds(20);
        String chron_exp = '' + sysTime.second() + ' ' + sysTime.minute() + ' ' + sysTime.hour() + ' ' + sysTime.day() + ' ' + sysTime.month() + ' ? ' + sysTime.year();
        system.debug(chron_exp);
        AccountBatch2Scheduler acctBatch2Sched = new AccountBatch2Scheduler();
        //Schedule the next job, and give it the system time so name is unique
        System.schedule('acctBatch2Job' + sysTime.getTime(),chron_exp,acctBatch2Sched);
   }
}

BATCH APEX 2 - The second batch Apex process.


global class AccountBatch2 implements Database.Batchable<sobject>
{
   global final String Query;
   global final String Entity;
   global final String Field;
   global final String Value;
   global AccountBatch2(String q)
   {
             Query=q;
   }
   global Database.QueryLocator start(Database.BatchableContext BC)
   {
      return Database.getQueryLocator(query);
   }
   global void execute(Database.BatchableContext BC,List<sObject> scope)
   {
         List<Account> updateAccts = new List<Account>();
      for(Sobject s : scope)
      {
          Account a = (Account) s;
          a.Name = a.Name + 'Batch 1.';
      }     
      update updateAccts;
   }


   global void finish(Database.BatchableContext BC)
   {
        System.debug(LoggingLevel.WARN,'Batch Process 2 Finished');
   }
}

SCHEDULE APEX - Just execute the next batch Apex.

global class AccountBatch2Scheduler implements Schedulable
{   global void execute(SchedulableContext ctx)
   {
        AccountBatch2 acctb2 = new AccountBatch2('Select Id, Name from Account');
        ID batchprocessid = Database.executeBatch(acctb2,20);
   }  
}

Leveraging these classes, you can continually execute this batch sequence. I have included some of the monitoring logs to show the output of this process.

Here you can see the batch Jobs were executed:



Here you can see the scheduled Apex for executing the second batch:

This is not the only solution to the problem, but it is one that I personally prefer. You can also use Salesforce Email Services if you so desire, however I prefer this approach.

I have an idea posted on the Salesforce Idea Exchange to allow Batch Apex to call another Batch Apex directly from the finish() method. You can vote for it here: Batch Apex invoke from Finish Method of another Batch Apex.