0

I have batch class that I'm using to add Contacts to a Campaign. I could have up to 6000 contacts I need to add at one time. I'm calling the batch from another controller class.

Question is for batch apex do list work the same way as updating a record or inserting a record. Let's say in the batch execute I add the members to a list and then insert the list. Will Apex break up that list and add members via the scope (I have it as 1000). Here is the code I have for the batch. It works fine in the Sandbox, but I'm only working with a few contacts.

UPDATED CODE

Batch Class

global class BatchAddToCampaign implements Database.Batchable<sObject>, Database.Stateful
{
Id campaignId {get; set;}
Set<Id> qKeys {get; set;}

global BatchAddToCampaign(Id campaignId, Set<Id> qKeys){
    this.campaignId = campaignId;
    this.qKeys = qKeys;
}

global Database.QueryLocator start(Database.BatchableContext BC)
{
    return Database.getQueryLocator('Select Id, Name, AccountId, Email From Contact Where Id In :qKeys');
}

global void execute(Database.BatchableContext BC, List<Contact> scope)
{
     List<CampaignMember> cm = new List<CampaignMember>();   
     for(Contact c : scope)
     {
          CampaignMember newCM = new CampaignMember(
            CampaignId = campaignId,
            ContactId = c.Id,
            status = 'Sent' );                
            cm.add(newCM);       
     }
     try {
     Database.insert(cm); 
     } catch (Exception ex) {
        System.Debug('Insert failed ' + ex);
     }
}   
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('Add to Campaign ' + a.Status);
    String campaignURL = URL.getSalesforceBaseUrl().toExternalForm() + '/' + campaignId;
    mail.setPlainTextBody('The batch Apex job processed ' + a.TotalJobItems + ' batches with '+ a.NumberOfErrors + ' failures. '
                          + 'Link to Campaign: ' + campaignURL);
    Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
}

Test Class

@isTest
private class TestSupportBatchCampaign {

static testMethod void testBatchCampaign() {

    Account a = new Account(Name='Support');
    insert a;
    Contact c = new Contact(AccountID = a.Id, FirstName = 'TestFirst', LastName='TestLast', Email='test@test.com');
    insert c;
    Map<Id, Contact> conMap = new Map<Id, Contact>([Select Id, Name, Email From Contact Where Id = :c.id]);
    Campaign cp = new Campaign(Name='SupportBatchTestCampaign', Goal__c='Exposure');     
    insert cp;

    Set<Id> qKeys = conMap.keyset();
    Id campaignId = cp.Id;

    List<CampaignMember> cm = new List<CampaignMember>(); 
    List<Contact>  cList = new List<Contact>();
    cList.add(c);
    for(Contact con : cList)
    {
         CampaignMember newCM = new CampaignMember(
            CampaignId = campaignId,
            ContactId = c.Id,
            status = 'Sent' );             
            cm.add(newCM);                             
     }
     Database.insert(cm);

     Test.startTest();
     Id batchId = Database.executeBatch(new BatchAddToCampaign(campaignId, qKeys));
     AsyncApexJob aj = [SELECT Id, Status, CreatedBy.Email FROM AsyncApexJob WHERE Id = :batchId];
     Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
     String[] toAddresses = new String[] {aj.CreatedBy.Email};
     mail.setToAddresses(toAddresses);
     mail.setSubject('test');       
     mail.setPlainTextBody('test');     
     Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
     Test.stopTest();       
}
}
  • 1
    as a side comment, (1) you should think about using the implements Database.stateful interface so at the end of the batch, in the finish() method, you can send an email or post to a log object the total execution results; and (2) replace your insert cm with Database.insert with support for partial successes so no one record blows up the 1000 records due to a validation error – cropredy May 13 '14 at 22:02
  • Thanks for the tip. I just implemented your suggestion and it worked great. I was able to send an email after the job completed. I think this is the last part I needed to fix. I was able to fix the view state limitation in VF by using static variables wherever possible. – ajl7519 May 14 '14 at 16:04
  • 1
    I updated the code. I actually found a good reference here developer.force.com/cookbook/recipe/… – ajl7519 May 14 '14 at 18:47
5

One reason to use a batch is to stay within the platform governor limits. The critical one here is probably that you can't do DML (data modification on more than 10,000 records in one transaction. That includes DML done in for examples triggers that have been added to CampaignMember too.

Yes if you process all 6,000 Contacts, they will be processed 1,000 at a time (courtesy of the batch framework) and each batch will get its own set of governor limits. That is the payback for accepting that the processing will be done asynchronously. This leaves x10 headroom for other code which is a lot. A good thing about using batches is that it is easy to reduce the batch size (as its just one parameter that could even come from a custom setting) if you run into trouble later when more logic has been added.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.