先理解一下定义:
所谓异步,是指在进行输入输出处理时,不必等到输入输出处理完毕才返回。所以异步的同义语是非阻塞(None Blocking)。
举个例子:普通B/S模式(同步)AJAX技术(异步)
同步:提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事
异步: 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕
同步就是你叫我去吃饭,我听到了就和你去吃饭;如果没有听到,你就不停的叫,直到我告诉你听到了,才一起去吃饭。
异步就是你叫我,然后自己去吃饭,我得到消息后可能立即走,也可能等到下班才去吃饭。
所以,要我请你吃饭就用同步的方法,要请我吃饭就用异步的方法。
以通讯为例
同步:发送一个请求,等待返回,然后再发送下一个请求
异步:发送一个请求,不等待返回,随时可以再发送下一个请求 ;在CPU空闲时候 执行。
并发:同时发送多个请求
salesforce本身对于很多数据操作的次数均有严格的限制:
Number of SOQL queries: 100 -->一次执行SOQL的次数不能超过100次
Number of query rows: 50000 -->一次查出的数据行数不能超过50000条
Number of SOSL queries: 20 -->一次执行SOSL次数不能超过20次
Number of DML statements: 150 -->DML语句不能超过150条
Number of DML rows: 10000 -->一次操作数据行数不能超过10000行
Maximum CPU time: 10000 -->最大的CPU时间不能超过10000ms
Maximum heap size: 6000000 -->堆大小不能超过6000000B
Number of callouts:100 -->一次执行callouts次数不能超过100次
Number of Email Invocations: 10 -->Email调用次数不能超过10次
Number of future calls: 50 -->调用Future次数不能超过50次
Number of queueable jobs added to the queue:50 -->添加到队列的queueable job数量不能超过50次
Number of Mobile Apex push calls: 10 -->移动端Apex push调用最多不能超过10次
所以,在我们想处理大批量数据的时候,或者是担心CPU 超时,就要考虑使用异步处理了;
Salesforce 异步处理包括几种类型:
Future方法:在自己线程中运行,直到资源可用才运行 用于Web service callout.
Batch Apex:运行大量的Job,数量超过正常处理限制 用于数据DML操作
QueueableApex:和Future类似,但是提供额外的工作链,允许完成更复杂的类型 用于执行顺序处理操作与外部Web服务。
ScheduledApex:指定时间运行apex 固定时间的任务,例如每日或每周等任务
数据批处理Batchable:
start()用于查询数据,并将查询数据封装到List中
用于收集要传递给接口方法的记录或对象执行进行处理。此方法在Batch Apex作业开始时调用一次,并返回一个Database.QueryLocator对象或一个包含传递给作业的记录或对象的Iterable。大多数时候,QueryLocator使用简单的SOQL查询来生成批处理作业中对象的范围。但是如果你需要做一些疯狂的事情,在传递给execute方法之前循环遍历API调用或预处理记录的结果,你可能想要查看参考资料部分中的Custom Iterators链接。使用QueryLocator对象,绕过由SOQL查询检索的记录总数的控制器限制,您可以查询多达5000万条记录。但是,对于Iterable,仍会强制执行由SOQL查询检索的记录总数的控制器限制。
execute():用于操作数据,形参中List为start()方法中返回的数据,可以直接对此List进行修改以达到批处理行为
对传递给方法的每个数据块或“批次”数据执行实际处理。默认批处理大小为200条记录。不能保证记录的批次按照从start方法接收的顺序执行。
此方法采用以下方法:
对Database.BatchableContext对象的引用。
sObject的列表,例如List <sObject>,或参数化类型的列表。如果使用Database.QueryLocator,请使用返回的列表。
finish():后期处理
用于执行后处理操作(例如,发送电子邮件),并在处理所有批次后调用一次。 这三步 称为一个周期。
模板:
global class MyBatchClass implements Database.Batchable<sObject> {
global (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {
// collect the batches of records or objects to be passed to execute
}
global void execute(Database.BatchableContext bc, List<P> records){
// process each batch of records
}
global void finish(Database.BatchableContext bc){
// execute any post-processing operations
}
}
案例:
global class UpdateContactAddresses implements
Database.Batchable<sObject>, Database.Stateful {
// instance member to retain state across transactions
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){
// process each batch of records
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;
// add contact to list to be updated
contacts.add(contact);
// increment the instance member counter
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()];
// call some utility to send email
EmailUtils.sendMessage(a, recordsProcessed);
}
}
Testing Batch Apex:
@isTest
private class UpdateContactAddressesTest {
@testSetup
static void setup() {
List<Account> accounts = new List<Account>();
List<Contact> contacts = new List<Contact>();
// insert 10 accounts
for (Integer i=0;i<10;i++) {
accounts.add(new Account(name='Account '+i,
billingcity='New York', billingcountry='USA'));
}
insert accounts;
// find the account just inserted. add contact for each
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();
// after the testing stops, assert records were updated properly
System.assertEquals(10, [select count() from contact where MailingCity = 'New York']);
}
}