Salesforce使用Batch Class

场景描述:UAT过后,我们需要将客户的历史数据导进生产环境,由于记录体量很大,通常会先关闭部分Trigger,Process Builder,Workflow等来保证数据能成功导入,事后,为了保证业务数据的合理性,我们会使用Batch来更新那些被禁用后的逻辑。
Template Code:
Batch class:

global class ExampleBatchClass implements Database.Batchable{
    global ExampleBatchClass(){
        // Batch Constructor
    }
    // Start Method
    global Database.QueryLocator start(Database.BatchableContext BC){
        return Database.getQueryLocator(query);
    }
    // Execute Logic
    global void execute(Database.BatchableContext BC, Listscope){
        // Logic to be Executed batch wise      
    }
    global void finish(Database.BatchableContext BC){
        // Logic to be Executed at finish
    }
}

Call the batch class:

ExampleBatchClass b = new ExampleBatchClass(); 
//Parameters of ExecuteBatch(context,BatchSize)
database.executebatch(b,10);

Note:
1. if batch size is not mentioned it is 200 by default.

2. 我们知道future方法不能在batch或者future方法中被调用,但是如果在trigger中调用future是可以的,前提是该trigger不能由batch触发。简言之,如果batch触发了trigger,而trigger调用了future不被允许,错误如下:
caused by: System.AsyncException: Future method cannot be called from a future or batch method

Sample:将Org中所有Lead历史数据的Status更新为Closed
Code Snap:

global class LeadProcessor implements Database.Batchable, Database.Stateful {
	// instance member to retain state across transactions
    global Integer recordsProcessed = 0;
    global Database.QueryLocator start(Database.BatchableContext bc) {
        // collect the batches of records or objects to be passed to execute
        String str = 'select Id, Status from Lead';
        return Database.getQueryLocator(str);
    }

    global void execute(Database.BatchableContext bc, List records){
        // process each batch of records
        List ldList = new List();
        for(Lead l : records) {
            l.Status = 'Closed';
            ldList.add(l);
            recordsProcessed ++;
        }
        update ldList;
    }    

    global void finish(Database.BatchableContext bc){
        // execute any post-processing operations
        System.debug(recordsProcessed + ' records processed.');
        AsyncApexJob job = [SELECT Id, Status, NumberOfErrors, 
                            JobItemsProcessed,
                            TotalJobItems, CreatedBy.Email
                            FROM AsyncApexJob
                            WHERE Id = :bc.getJobId()];
    }    

}

finish方法在batch执行完毕执行,一般用作邮件通知,代码如下:

global void finish(Database.BatchableContext BC){
   // 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 =
      :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('Apex Sharing Recalculation ' + a.Status);
   mail.setPlainTextBody
   ('The batch Apex job processed ' + a.TotalJobItems +
   ' batches with '+ a.NumberOfErrors + ' failures.');
   Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}

Test Code:

@isTest
private class LeadProcessorTest {
    @testSetup 
    static void setup() {
        List ldList = new List();
        for(Integer i = 0; i < 200; i++) {
            ldList.add(new Lead(LastName = 'Lead' + i, Company = 'com' + i, Status = 'Open'));
        }
        insert ldList;
    }
    static testMethod void test1() {
        Test.startTest();
        LeadProcessor lp = new LeadProcessor();
        Id batchId = Database.executeBatch(lp);
        Test.stopTest();
    }
}

在Developer Console的Open Excute Anonymous Window执行以下代码:

LeadProcessor leadBach = new LeadProcessor(); 
Id batchId = Database.executeBatch(leadBach, 100);

参考资料:
https://trailhead.salesforce.com/modules/asynchronous_apex/units/async_apex_batch
补充:
Batch特性:如果我们在start里面写查询语句,如果查询中包含关系字段(除关系ID外),那么debug时是出不来关系字段的值的,这时候如果用公式字段代替关系字段,同样debug不出值;如果我们在excute里面做查询,那么debug出来的只有ID和Name的值,其他字段值也出不来。

举例:我们需要处理Account和Contact,在start里面查询语句为:select id, name, accountid, account.site from contact,那么只能debug出id, name, accountid的值;如果我们在excute里面写入:select id, name, site from account,只能debug出id和name的值。
建议:如果是跨对象的过滤,首先将子对象记录在start中查处来(Id),然后在excute里面将查出来的id作为参数传入外部异步方法,该方法主要用来使用select再次查询,这个时候就可以debug出关系字段了,意思是就可以做过滤了。
参考文档:http://www.iterativelogic.com/developing-custom-apex-rollup-summary-field-logic/

纠偏与建议篇180802】:
早期偏见:batch查询跨对象(父查子,子查父),那么由于debug不出跨对象数据,那么自然无法使用此类值做逻辑判断。
下面代码两种组织思维(见注释/非注释部分),同样实现了将所有子记录的Name值以;隔开拼起来填到父记录某字段的需求。

global class BatchAccountLoginsPopulate implements Database.Batchable {

    global Integer recordsProcessed = 0;

    global Database.QueryLocator start(Database.BatchableContext bc) {
        // String query = 'SELECT Id, Account__c FROM MT4_Accounts__c';
        String query = 'SELECT Id, Name, Trading_Account_Login__c, (SELECT Id FROM MT4_Accounts__r) FROM Account WHERE Trading_Account_Login__c  = NULL';
        return Database.getQueryLocator(query);
    }

    // global void execute(Database.BatchableContext BC, list records) {
    global void execute(Database.BatchableContext BC, list records) {
        // List accList = new List();
        // Set accIdSet = new Set();
        // for(MT4_Accounts__c rAcc : records) {
        //     accIdSet.add(rAcc.Account__c);
        // }
        // if(accIdSet.size() > 0) {
        //     TradingAccountTriggerFunction.joinLoginToAccount(accIdSet);
        // }
        Set accIds = new Set();
        for(Account acc : records) {
            if(acc.MT4_Accounts__r.size() > 0) {
                accIds.add(acc.Id);
            }
        }
        if(accIds.size() > 0) {
            TradingAccountTriggerFunction.joinLoginToAccount(accIds);
        }
    }

    global void finish(Database.BatchableContext BC) {
        AsyncApexJob a = [SELECT Id, Status, NumberOfErrors, JobItemsProcessed,
                          TotalJobItems, CreatedBy.Email
                          FROM AsyncApexJob WHERE Id = :BC.getJobId()];
        System.debug('[Apex Sharing Recalculation ' + a.Status + ']: The batch Apex job processed ' + a.TotalJobItems +
        ' batches with '+ a.NumberOfErrors + ' failures.');
    }
}

写测试类可能遇到如下错误:原因是该字段字段类型为长文本字段

System.QueryException: field 'Trading_Account_Login__c' can not be filtered in a query call

实践总结180802】:
batch最佳实践是query部分最好以参数形式传进来,hard code会导致不灵活,如果部署到了生产环境刷数据如果有最新查询方案,动态传参会省大笔修改逻辑,写测试类,重新部署,修正错误等时间。
Execute All Batch Classes180814】:

One Universal Schedule Class to Execute All Batch Classes In Salesforce

Are you creating schedule class for each batch class if you need to schedule that?

If yes, stop doing like that and start creating a universal (common scheduler class) like below and use it instead of creating multiple schedules.

Step 1: 
Create a universal scheduler class

global class UniversalScheduler implements Schedulable {
	global Database.Batchable batchClass{get;set;}
    global Integer batchSize{get;set;} {batchSize = 200;}

    global void execute(SchedulableContext sc) {
    	Database.executebatch(batchClass, batchSize);
    }
}

Step 2:

Let say, You have two batch classes and you want to schedule, then schedule batch class like below using UniversalScheduler class

Batch Class 1:

AccountBatchProcess accBatch = new AccountBatchProcess(); // Batch Class Name
UniversalScheduler scheduler = new UniversalScheduler();
scheduler.batchClass = accBatch;
scheduler.batchSize = 100;
String sch = '0 45 0/1 1/1 * ? *';
System.schedule('Account Batch Process Scheduler', sch, scheduler);

Batch Class 2:

ContactBatchProcess cntBatch = new ContactBatchProcess(); // Batch Class Name
UniversalScheduler scheduler = new UniversalScheduler();
scheduler.batchClass = cntBatch;
scheduler.batchSize = 500;
String sch = '0 45 0/1 1/1 * ? *';
System.schedule('Contact Batch Process Scheduler', sch, scheduler);

每隔5min执行一次Schedule方法】:

System.schedule('IMS_ScheduleCampMemberStatusUpdate 1',  '0 00 * * * ?', new IMS_ScheduleCampMemberStatusUpdate());
    System.schedule('IMS_ScheduleCampMemberStatusUpdate 2',  '0 05 * * * ?', new IMS_ScheduleCampMemberStatusUpdate());
    System.schedule('IMS_ScheduleCampMemberStatusUpdate 3',  '0 10 * * * ?', new IMS_ScheduleCampMemberStatusUpdate());
    System.schedule('IMS_ScheduleCampMemberStatusUpdate 4',  '0 15 * * * ?', new IMS_ScheduleCampMemberStatusUpdate());
    System.schedule('IMS_ScheduleCampMemberStatusUpdate 5',  '0 20 * * * ?', new IMS_ScheduleCampMemberStatusUpdate());
    System.schedule('IMS_ScheduleCampMemberStatusUpdate 6',  '0 25 * * * ?', new IMS_ScheduleCampMemberStatusUpdate());
    System.schedule('IMS_ScheduleCampMemberStatusUpdate 7',  '0 30 * * * ?', new IMS_ScheduleCampMemberStatusUpdate());
    System.schedule('IMS_ScheduleCampMemberStatusUpdate 8',  '0 35 * * * ?', new IMS_ScheduleCampMemberStatusUpdate());
    System.schedule('IMS_ScheduleCampMemberStatusUpdate 9',  '0 40 * * * ?', new IMS_ScheduleCampMemberStatusUpdate());
    System.schedule('IMS_ScheduleCampMemberStatusUpdate 10', '0 45 * * * ?', new IMS_ScheduleCampMemberStatusUpdate());
    System.schedule('IMS_ScheduleCampMemberStatusUpdate 11', '0 50 * * * ?', new IMS_ScheduleCampMemberStatusUpdate());
    System.schedule('IMS_ScheduleCampMemberStatusUpdate 12', '0 55 * * * ?', new IMS_ScheduleCampMemberStatusUpdate());

Apex批量删除Schedule方法】:
Salesforce使用Batch Class_第1张图片

List JOBIDLIST = new List();
JOBIDLIST = [SELECT Id, CronJobDetail.name, NextFireTime, PreviousFireTime, State, StartTime, EndTime, CronExpression FROM CronTrigger where CronJobDetail.name like 'IMS_Schedule%'];
for(CronTrigger job:JOBIDLIST )
    System.abortJob(job.id);

【其他话题】:
1. Using Aggregate SOQL queries/results in Batch Apex
2. Executing Batch Apex in Sequence

 

 

 

 

 

 

 

 

 

 


 

你可能感兴趣的:(Sales,Cloud,Salesforce,Experience)