在salesforce中,想要实现批处理,可以使用salesforce提供的Database.Batchable接口。
Batch apex
batch apex也是一个apex,只是继承了Database.Batchable接口。
public class SearchAndReplace implements Database.Batchable<sObject>{
public Database.QueryLocator start(Database.BatchableContext bc){
return Database.getQueryLocator(query);
}
public void execute(Database.BatchableContext bc, List<sObject> scope){
for(sobject s : scope){
s.put(Field,Value);
}
update scope;
}
public void finish(Database.BatchableContext bc){
}
}
- batch apex 必须被定义为public或global类。
里面有三个必要的方法:start、execute、finish。
1.start
public Database.QueryLocator start(Database.BatchableContext bc){
String query = 'SELECT Id FROM Account LIMIT 20';
return Database.getQueryLocator(query);
}
start方法在批处理开始时调用,将查询到的结果传给execute方法。
- 使用 QueryLocator 对象,会绕过 SOQL 查询检索的记录总数的限制。
- 每执行一次batch跑一次start方法。
- Database.QueryLocator 对象中最多可以返回 5000 万条记录。 如果返回的记录超过 5000 万条,则批处理作业将立即终止并标记为“Failed”。
- 在start中执行的soql最好不要使用子查询。在excute中而不是在start中使用子查询可以提高执行速度。
2.execute
public void execute(Database.BatchableContext bc, List<Account> scope){
for(Account acc : scope){
acc.Name = 'test';
}
update scope;
}
对于传进来的每批记录,都会执行 execute方法。
- execute处理从start中传进来的数据时,处理的顺序不能保证。
- 如果执行一次batch处理1000条数据,batch size是200,那么跑5次execute方法,也就是5个事务,每个事务中处理200条数据。如果第一个事务成功,第二个事务失败,那么第一个事务中对数据的更新不会回滚。
- salesforce推荐在excute中重新查询该事务中处理的数据,并使用FOR UPDATE将数据锁定,避免更新冲突。重新查询只需要ID即可。
Account [] accts = [SELECT Id FROM Account LIMIT 2 FOR UPDATE];
3.finish
public void finish(Database.BatchableContext bc){
AsyncApexJob a = [SELECT Id, Status, NumberOfErrors, JobItemsProcessed,
TotalJobItems, CreatedBy.Email
FROM AsyncApexJob WHERE Id = :bc.getJobId()];
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 });
}
finish方法在所有批次数据都处理完毕后调用,可用于发邮件等操作,也可以是空的。
- 也可以在finish中执行另外一个新的batch。
- 每执行一次batch跑一次finish方法。
执行batch
1.使用Database.executeBatch来提交batch job。
Database.executeBatch需要包含两个参数,一个是要执行的batch,另一个是batch size。batch size不传默认是200,最大值是2000。返回值是AsyncApexJob表的ID。
Id batchInstanceId = Database.executeBatch(new UpdateAccountFields(), 5);
AsyncApexJob aaj = [SELECT Id, Status, JobItemsProcessed, TotalJobItems,NumberOfErrors
FROM AsyncApexJob WHERE ID =: batchInstanceId ];
- 当我们调用Database.executeBatch来执行batch时,salesforce会把程序挂到队列上排队,实际执行可能会延迟。
2.使用 System.scheduleBatch 方法安排批处理作业在将来的某个时间运行一次。
System.scheduleBatch有4个参数。第一个是要执行的batch,第二个是job name,第三个是多少分钟后执行,第4个是batch size。不传默认是200,最大是2000。返回值是CronTrigger表的ID。
String cronID = System.scheduleBatch(new UpdateAccountFields(), 'jobName', 60, 5);
CronTrigger ct = [SELECT Id, TimesTriggered, NextFireTime
FROM CronTrigger WHERE Id = :cronID];
Database.Stateful
batch的每个事务之间是不能互相通信的,使用Database.Stateful使变量在事务之间保留其值。静态变量不保留其值,并在事务之间重置。该功能常用于计数、汇总等。
public class SummarizeAccountTotal implements
Database.Batchable<sObject>, Database.Stateful{
public final String Query;
public integer Summary;
public SummarizeAccountTotal(String q){
Query=q;
Summary = 0;
}
public Database.QueryLocator start(Database.BatchableContext bc){
return Database.getQueryLocator(query);
}
public void execute(Database.BatchableContext bc, List<sObject> scope){
for(sObject s : scope){
Summary = Integer.valueOf(s.get('total__c'))+Summary;
}
}
public void finish(Database.BatchableContext bc){
}
}
Batch apex的测试
关于batch apex的test class也有一些需要注意的
- 在test class里只能跑一次excute方法,也就是说,如果batch size是200,有201条数据,test class会报错。
- 因为executeBatch是异步方法,所以要确保在执行之后再判断执行的结果,把executeBatch写在startTest和stopTest之间。
Test.StartTest();
OwnerReassignment reassign = new OwnerReassignment();
ID batchprocessid = Database.executeBatch(reassign);
Test.StopTest();