Apex-异步

Apex 异步(3)批处理

学习目标

完成这个单位后,你会知道:

  • 在哪里使用Batch Apex。
  • 使用批次时Apex限制较高。
  • Batch Apex语法。
  • 批量Apex最佳实践。

Batch Apex

Batch Apex用于运行大型工作(认为数千或数百万条记录!),这将超出正常的处理限制。使用Batch Apex,您可以批量异步处理记录(因此名称为“Batch Apex”)以保持平台限制。如果您有很多记录要处理,例如数据清理或归档,Batch Apex可能是您最好的解决方案。

Batch Apex的工作原理如下。假设您想使用Batch Apex处理100万条记录。对于您正在处理的每批记录,批处理类的执行逻辑被调用一次。每次调用批次类时,作业将被放置在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);
每个批次的Apex调用都将创建一个AsyncApexJob记录,以便您可以跟踪作业的进度。您可以通过SOQL查看进度或在Apex作业队列中管理作业。我们马上谈谈Job Queue。
AsyncApexJob job = [SELECT Id, Status, JobItemsProcessed, TotalJobItems, NumberOfErrors FROM AsyncApexJob WHERE ID = :batchId ];
Batch Apex使用状态

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']);
    }
    
}
设置方法插入10个帐户记录,开票城市为“纽约”,开票国家为“美国”。然后为每个帐户创建一个关联的联系人记录。该数据由批次类使用。

注意

确保插入的记录数小于批量大小200,因为测试方法只能执行一个批次总数。

在测试方法中,UpdateContactAddresses批次类被实例化,通过调用Database.executeBatch并将其传递给批次类的实例来调用。

对Database.executeBatch的调用包含在Test.startTest和Test.stopTest块中。这就是所有魔法发生的地方。在调用Test.stopTest之后执行作业。包含在Test.startTest和Test.stopTest中的任何异步代码都是在Test.stopTest之后同步执行的。

最后,测试通过检查结算城市“纽约”的联系人记录数量与插入的记录数量(即10)相匹配来验证所有联系人记录已被正确更新。

最佳实践

与未来的方法一样,在使用Batch Apex时,您还是要记住一些事情。为确保快速执行批处理作业,请尽量减少Web服务调出时间并调整批处理Apex代码中使用的查询。批处理作业执行的时间越长,当许多作业在队列中时,其他排队的作业就越有可能被延迟。最佳实践包括:

  • 如果您有多个批次的记录,请仅使用Batch Apex。如果您没有足够的记录来运行多个批次,则最好使用Queueable Apex。
  • 调整任何SOQL查询以尽可能快地收集要执行的记录。
  • 尽量减少创建的异步请求的数量,以尽量减少延迟的机会。
  • 如果您打算从触发器调用批处理作业,请特别小心。您必须能够保证触发器不会添加比限制更多的批处理作业。

你可能也会喜欢...