学习目标
- 在哪里使用Batch Apex。
- 使用批次时Apex限制较高。
- Batch Apex语法。
- 批量Apex最佳实践。
Batch Apex
Batch Apex用于运行大型工作(认为数千或数百万条记录!),这将超出正常的处理限制。使用Batch Apex,您可以批量异步处理记录(因此名称为“Batch Apex”)以保持平台限制。如果您有很多记录要处理,例如数据清理或归档,Batch Apex可能是您最好的解决方案。
- 每笔交易都以一组新的限额开始,更容易确保您的代码保持在限额执行范围内。
- 如果一个批处理失败,则所有其他成功的批处理事务都不会回滚。
Batch Apex语法
要编写批量Apex类,您的类必须实现Database.Batchable接口并包含以下三种方法:
- start
- 用于收集要传递给接口方法的记录或对象执行处理。此方法在Batch Apex作业开始时调用一次,并返回一个Database.QueryLocator对象或一个包含传递给作业的记录或对象的Iterable。
大多数情况下,QueryLocator通过简单的SOQL查询来生成批量作业中的对象范围。但是,如果您需要做一些疯狂的事情,比如在传递给execute方法之前循环调用API的结果或预处理记录,则可能需要查看参考资料部分中的自定义迭代器链接。
使用QueryLocator对象,SOQL查询检索的记录总数限制被绕过,并且您可以查询多达5000万条记录。但是,对于Iterable,由SOQL查询检索的记录总数的限制仍然是强制执行的。
- execute
-
对传递给方法的每个块或“批量”数据执行实际处理。默认的批量大小是200条记录。不保证记录批按从启动方法收到的顺序执行。
该方法采取以下措施:- 对Database.BatchableContext对象的引用。
- sObject的列表,例如List <sObject>或者参数化类型列表。如果您正在使用Database.QueryLocator,请使用返回的列表。
- finish
- 用于执行后处理操作(例如,发送电子邮件)并在所有批处理完成后调用一次。
以下是Batch Apex类的框架:
global class MyBatchClass implements Database.Batchable<sObject> {
global (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {
// 收集要传递的记录或对象的批处理执行
}
global void execute(Database.BatchableContext bc, List<P> records){
// 处理每批记录
}
global void finish(Database.BatchableContext bc){
// 执行任何后期处理操作
}
}
调用批处理类
要调用批处理类,只需实例化它,然后调用Database.executeBatch与实例:
MyBatchClass myBatchObject = new MyBatchClass();
Id batchId = Database.executeBatch(myBatchObject);
Id batchId = Database.executeBatch(myBatchObject, 100);
AsyncApexJob job = [SELECT Id, Status, JobItemsProcessed, TotalJobItems, NumberOfErrors FROM AsyncApexJob WHERE ID = :batchId ];
Batch Apex通常是无状态的。批量Apex作业的每次执行都被视为离散事务。例如,包含1,000条记录并使用默认批量大小的批量Apex作业被视为每个200条记录的五个事务。
如果在类定义中指定Database.Stateful,则可以在所有事务中维护状态。当使用Database.Stateful时,只有实例成员变量在事务之间保留它们的值。维护状态对于计算或汇总处理记录非常有用。在下一个示例中,我们将更新批处理作业中的联系人记录,并要跟踪受影响的总记录,以便我们可以将其包含在通知电子邮件中。
Batch Apex例子代码
现在你已经知道如何编写一个Batch Apex类了,我们来看一个实际的例子。假设您有一项业务要求,即美国公司的所有联系人都必须将其母公司的帐单邮寄地址作为邮寄地址。不幸的是,用户输入的新联系人没有正确的地址!用户不会学习?!编写一个批处理Apex类,确保这个要求被强制执行。
以下示例类查找使用QueryLocator由start()方法传入的所有帐户记录,并使用其帐户的邮寄地址更新关联的联系人。最后,它发送一个包含批量作业结果的电子邮件,因为我们使用Database.Stateful来跟踪状态,所以更新记录的数量。
global class UpdateContactAddresses implements
Database.Batchable<sObject>, Database.Stateful {
// 实例成员在事务中保持状态
global Integer recordsProcessed = 0;
global Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator(
'SELECT ID, BillingStreet, BillingCity, BillingState, ' +
'BillingPostalCode, (SELECT ID, MailingStreet, MailingCity, ' +
'MailingState, MailingPostalCode FROM Contacts) FROM Account ' +
'Where BillingCountry = \'USA\''
);
}
global void execute(Database.BatchableContext bc, List<Account> scope){
// 处理每批记录
List<Contact> contacts = new List<Contact>();
for (Account account : scope) {
for (Contact contact : account.contacts) {
contact.MailingStreet = account.BillingStreet;
contact.MailingCity = account.BillingCity;
contact.MailingState = account.BillingState;
contact.MailingPostalCode = account.BillingPostalCode;
// 添加联系人列表进行更新
contacts.add(contact);
// 增加实例成员计数器
recordsProcessed = recordsProcessed + 1;
}
}
update contacts;
}
global void finish(Database.BatchableContext bc){
System.debug(recordsProcessed + ' records processed. Shazam!');
AsyncApexJob job = [SELECT Id, Status, NumberOfErrors,
JobItemsProcessed,
TotalJobItems, CreatedBy.Email
FROM AsyncApexJob
WHERE Id = :bc.getJobId()];
// 调用一些实用程序发送电子邮件
EmailUtils.sendMessage(a, recordsProcessed);
}
}
- start方法提供了执行方法将在单个批处理中处理的所有记录的集合。它通过使用SOQL查询调用Database.getQueryLocator来返回要处理的记录列表。在这种情况下,我们只需查询“美国”的帐单国家/地区的所有帐户记录。
- 每个批次的200条记录都在执行方法的第二个参数中传递。 execute方法将每个联系人的邮寄地址设置为帐户的帐单地址,并增加recordsProcessed以跟踪处理的记录数。
- 作业完成后,完成方法将在AsyncApexJob对象(列出有关批处理作业的信息的表)上执行查询,以获取作业的状态,提交者的电子邮件地址以及其他信息。然后它会发送一封通知邮件给作业提交者,其中包括工作信息和联系人数量的更新。
测试Batch Apex
由于Apex开发和测试齐头并进,所以我们如何测试上述批次类。简而言之,我们插入一些记录,调用Batch Apex类,然后用正确的地址声明记录已经正确更新。
@isTest
private class UpdateContactAddressesTest {
@testSetup
static void setup() {
List<Account> accounts = new List<Account>();
List<Contact> contacts = new List<Contact>();
// 插入10个帐户
for (Integer i=0;i<10;i++) {
accounts.add(new Account(name='Account '+i,
billingcity='New York', billingcountry='USA'));
}
insert accounts;
// 找到刚插入的帐号。为每个添加联系人
for (Account account : [select id from account]) {
contacts.add(new Contact(firstname='first',
lastname='last', accountId=account.id));
}
insert contacts;
}
static testmethod void test() {
Test.startTest();
UpdateContactAddresses uca = new UpdateContactAddresses();
Id batchId = Database.executeBatch(uca);
Test.stopTest();
// 在测试停止之后,声明记录被正确更新
System.assertEquals(10, [select count() from contact where MailingCity = 'New York']);
}
}
最佳实践
- 如果您有多个批次的记录,请仅使用Batch Apex。如果您没有足够的记录来运行多个批次,则最好使用Queueable Apex。
- 调整任何SOQL查询以尽可能快地收集要执行的记录。
- 尽量减少创建的异步请求的数量,以尽量减少延迟的机会。
- 如果您打算从触发器调用批处理作业,请特别小心。您必须能够保证触发器不会添加比限制更多的批处理作业。