异步Apex类

一个Apex类可以定义为异步类,用于异步执行。

异步类可以通过多种方式实现:

  • Future注解
  • 批处理
  • Queueable接口
  • Schedulable接口

Future注解

使用Future注解可以将一个Apex函数定义为异步执行类。该类会拥有自己的线程,并在此线程中独立运行,实现异步效果。

Future注解的应用示例:

global class ExampleClass {
@future
public static void exampleFutureFunction(List<Id> recordIds) {
// ..
}
}

要注意的是,在Future函数中不可以用sObject对象作为参数,因为Future函数是独立运行的,如果将sObject对象传入其中,该sObject对象在Future函数运行的同时有可能被改变,这样有可能会造成错误。

定义为Future的函数必须是静态的(static),并且返回类型必须是void。

由于异步函数的特点,在多个Future函数同时执行的时候,执行的顺序是不确定的,完成的顺序也不确定。所以作为开发者,我们不能设想用一个Future函数的执行结果来影响另一个Future函数,也不能从Future函数中调用另一个Future函数。

在Apex函数中调用外部网络服务

在Apex函数中调用外部网络服务时,可以定义该函数为Future,并加入“callout=true”。比如:

@future(callout=true)
public static void callWebService() {
String result = ExampleWebServiceClass.getWebServiceResult();
}

通过这种方式,此函数不需要等待网络服务的回应,从而可以继续执行其他的功能。

Future函数单元测试

在对Future函数进行单元测试时,必须将测试的代码放入“startTest()”和“stopTest()”函数中间。“stopTest()”函数可以确保在异步执行的函数得到结果之后再继续执行后面的代码。比如:

@isTest
static void testExampleFutureFunction() {
// ... Test.startTest(); ExampleClass.exampleFutureFunction(); Test.stopTest(); // ...
}

最佳实践

由于Salesforce将所有异步函数保存在一个队列中执行,所以如果同时运行的Future函数过多,会导致异步执行的效率降低。

使用Future函数时,尽量保证该函数被尽快执行。

如果需要同时调用若干外部网络服务请求,尽量将这些请求放在一个Future函数中,而不要对每一个请求建立单独的Future函数。

批处理Apex

当开发者想在代码中处理大批量的数据时,很容易会使数据库查询的次数达到上限。为了避免这种情况,Apex提供了“Database.Batchable”接口,可以让开发者实现批处理Apex类,用于异步执行大批量数据的查询操作。

“Database.Batchable”接口包含了如下函数:

  • start():初始化函数,对要处理的数据进行初始化,并将要处理的数据作为返回值,分批传入execute()函数。返回的类型可以是“Database.QueryLocator”或“Iterable”
  • execute():从start()函数中分批接收数据,并对每批数据进行处理。数据被处理的顺序并不一定和从start()函数传入的顺序一致
  • finish():当execute()函数执行完成后,对数据进行最后的处理

一个标准的批处理类的结构:

global class ExampleBatchClass 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){
// 收尾函数
} }

执行批处理Apex类

使用“Database.executeBatch()”函数可以执行一个批处理Apex类。与此同时,还可以设定一个参数,指定从start()函数传入execute()函数的每批记录的最大数量(如果不设置则为200)。每次执行的时候,会返回一个ID类型的结果,通过这个ID可以在Salesforce中的AsyncApexJob对象中查询当前批处理的执行情况。

示例代码:

// 执行批处理类
ExampleBatchClass exampleBatchClass = new ExampleBatchClass(); // 不设定每批记录的最大数量并执行批处理操作
Id batchId = Database.executeBatch(exampleBatchClass); // 不设定每批记录的最大数量并执行批处理操作
Id batchIdWithVolumn = Database.executeBatch(exampleBatchClass, 300); // 查询批处理任务的进度
AsyncApexJob job = [SELECT
Id, Status, JobItemsProcessed, TotalJobItems, NumberOfErrors
FROM AsyncApexJob
WHERE ID = :batchId];

在批处理Apex类中记录数据的状态

通过实现“Database.Stateful”接口可以在批处理类中记录数据被处理的状态,并可以在类中设定非静态变量,该变量的值在处理每批数据时不会自动刷新。

示例代码:

global class ExampleBatchClass implements Database.Batchable<sObject>, Database.Stateful {

    // 定义一个非静态变量,记录execute执行的次数
global Integer executeProcessed = 0; global (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {
} global void execute(Database.BatchableContext bc, List<P> records){
// 每次执行execute()函数,便增加1
executeProcessed += 1;
} global void finish(Database.BatchableContext bc){
// 在此可以显示总共执行了多少次execute()函数
System.debug(executeProcessed);
} }

批处理Apex类的单元测试

对批处理Apex类进行单元测试时,与Future函数类似,执行代码要包含在“Test.startTest()”和“Test.stopTest()”函数中。并且,在单元测试中,只能处理一个批次的记录,所以在定义测试数据时,不要让测试数据总数超过200条。

Queueable接口

Queueable接口的作用和Future函数类似,但是实现了Queueable的类可以使用sObject对象作为参数进行操作,这一点Future函数做不到。

Queueable接口中定义了“execute()”函数。它必须是global或者public的。

“System.enqueueJob()”函数可以将实现了Queueable接口的类加入系统的队列,异步执行。

代码结构如下:

public class ExampleClass implements Queueable {
public void execute(QueueableContext context) {
// ...
}
}

示例代码:

首先定义一个Apex类,实现Queueable接口。该类的功能是对于每一个Account对象,设置parentId字段并更新:

public class UpdateParentAccount implements Queueable {

    private List<Account> accounts;
private ID parent; public UpdateParentAccount(List<Account> records, ID id) {
this.accounts = records;
this.parent = id;
} public void execute(QueueableContext context) {
for (Account account : accounts) {
account.parentId = parent;
}
update accounts;
} }

然后在代码中调用此类,实现异步操作。假设已经有了一个Account列表和一个ID值:

// 设定参数
UpdateParentAccount updateJob = new UpdateParentAccount(accountList, parentId); // 执行该操作
ID jobID = System.enqueueJob(updateJob);

通过此代码,即可实现异步将accountList列表中的Account对象的parentId字段更新为参数parentId的值。

实现Queueable的类的单元测试

对实现了Queueable的Apex类进行单元测试时,与Future函数类似,执行代码要包含在“Test.startTest()”和“Test.stopTest()”函数中。比如:

UpdateParentAccount updateJob = new UpdateParentAccount(accountList, parentId);

Test.startTest();
// 执行操作的代码要放在startTest()和stopTest()之间
ID jobID = System.enqueueJob(updateJob);
Test.stopTest();

任务的连续处理

Queueable接口的另一个优点是可以在execute()函数中调用另一个实现了Queueable的类,实现任务的连续处理。比如:

public class FirstJob implements Queueable {
public void execute(QueueableContext context) {
// 连接下一个任务
System.enqueueJob(new SecondJob());
}
}

任务的连续处理最多可以同时处理50个任务,并且在连接任务时,每个execute()函数中只能连接一个任务。

Schedulable接口

当一个类实现了Schedulable接口之后,可以被作为计划任务,在特定的时间执行。

Schedulable接口中定义了“execute()”函数。它必须是global或者public的。

代码结构如下:

global class SchedulableExampleClass implements Schedulable {
global void execute(SchedulableContext ctx) {
// ...
}
}

“System.Schedule()”或“System.scheduleBatch()”函数可以设置实现了Schedulable的类在某个特定时间执行。调用Schedulable类和调用Queueable类的方式类似:

示例代码:定义Apex类的计划执行

String CRON_EXP = '0 0 06 * * ?';
String jobId = System.schedule('Scheduled Job Name', CRON_EXP, new SchedulableExampleClass());

在上述代码中,使用了System.schedule()函数。它包含三个参数:

  • 第一个参数是计划任务的名字,可以自己定义,不能与已经存在的计划任务名字重复
  • 第二个参数是cron job的表达式,具体可以参考维基百科crontab。一个基本的解释:表达式包含7个部分,从左到右依次是:秒、分钟、小时、日、月、星期几、年,其中后两个是可选项。“*”表示所有可能的值,比如对于“小时”就是包括了0点到23点。“?”表示任一可能的值,比如对于“星期几”就是每周任意一天。
  • 第三个参数是要被计划的实现了Schedulable接口的类

关于cron job的表达式

其格式为:

Seconds Minutes Hours Day_of_month Month Day_of_week Optional_year

可以用问号(?)来表示“每一个”。比如上面代码中的“0 0 06 * * ?”就代表了每天6点。“0 0 10 ? * MON-FRI”就代表了周一到周五早上10点。

Apex类计划执行的特点

  • 所有计划都要定义一个执行的时间点,比如早上8点。如果在同一时间有多个Apex类被设定为计划执行,则它们其中的一部分的执行有可能有延迟
  • Salesforce中可以定义计划执行的Apex类最多是100个

用CronTrigger追踪计划任务

System.schedule()函数返回的值是ID类型,代表了当前计划任务的ID。使用CronTrigger对象可以追踪此任务的情况。

在知道计划任务ID的时候,可以使用以下查询语句:

CronTrigger ct = [SELECT TimesTriggered, NextFireTime FROM CronTrigger WHERE Id = :jobId];

如果在实现了Schedulable接口的Apex类中,在execute()函数里需要查询当前计划任务的ID,可以使用SchedulableContext的getTriggerId()函数。

CronTrigger ct = [SELECT TimesTriggered, NextFireTime FROM CronTrigger WHERE Id = :sc.getTriggerId()];

关于SchedulableContext的详细信息可以参考官方文档

用CronJobDetail得到计划任务的详细信息

CronJobDetail对象和CronTrigger对象相关联。可以使用如下查询得到计划任务的详细信息:

CronTrigger job = [SELECT Id, CronJobDetail.Id, CronJobDetail.Name, CronJobDetail.JobType
FROM CronTrigger
ORDER BY CreatedDate DESC
LIMIT 10];

注意这里的CronJobDetail.JobType字段,此字段代表了所有任务的类型,对于Apex计划任务,此字段的值为7。

比如:

SELECT COUNT() FROM CronTrigger WHERE CronJobDetail.JobType = '7'

单元测试Apex计划任务

对实现了Schedulable的Apex类进行单元测试时,与Future函数类似,执行代码要包含在“Test.startTest()”和“Test.stopTest()”函数中。比如:

// 定义计划任务类
global class SchedulableExampleClass implements Schedulable {
global void execute(SchedulableContext sc) {
Account a = [SELECT Id, Name FROM Account WHERE Name = 'TEST ACCOUNT'];
a.Name = 'CHANGED NAME'; update a;
}
} // 单元测试类
@isTest
class TestScheduleExample {
@isTest
static void testExample() { // 开始测试
Test.startTest(); // 建立测试数据
Account a = new Account();
a.Name = 'TEST ACCOUNT';
insert a; // 建立计划任务的执行
String jobId = System.schedule('Scheduled Job', '0 0 0 3 9 ? 2022', new SchedulableExampleClass()); // 得到计划任务的信息
CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime
FROM CronTrigger
WHERE Id = :jobId]; // 检查计划任务的Cron表达式是否相同
System.assertEquals('0 0 0 3 9 ? 2022', ct.CronExpression); // 检查计划任务是否尚未运行
System.assertEquals(0, ct.TimesTriggered); // 检查计划任务下次运行时间
System.assertEquals('2022-09-03 00:00:00', String.valueOf(ct.NextFireTime)); // 检查数据是否尚未改变
System.assertNotEquals('CHANGED NAME', [SELECT Id, Name FROM Account WHERE Id = :a.Id].Name); // 完成测试
Test.stopTest();
// 在stopTest()执行之后,已经计划的任务会忽略Cron表达式定义的时间立即执行,并且是同步执行,所以可以保证在执行接下来的语句之前,计划的Apex类的功能已经完成了 // 检查数据是否改变了
System.assertEquals('CHANGED NAME', [SELECT Id, Name FROM Account WHERE Id = :a.Id].Name);
}
}

通过设置界面设置Apex类计划执行

在“设置”界面中,搜索“Apex 类”,可以进入“Apex 类”界面。在此界面中列出了所有系统中存在的Apex类。点击“计划Apex”按钮,即可计划一个实现了Schedulable接口的Apex类,以每周或每月为间隔自动执行。

异步 Apex 类的更多相关文章

  1. 从网络服务生成Apex类

    使用WSDL2Apex从网络服务生成Apex类 如果某个网络服务被定义在WSDL文件中,而Salesforce必须使用SOAP和网络服务进行通信,则这种情况在某些时候会为开发者带来很多麻烦.为了简化S ...

  2. C# 异步工具类 及一点小小的重构经验

    2015年新年第一篇随笔, 祝福虽然有些晚,但诚意还在:新年快乐. 今天主要是想分享一异步工具类,在C/S架构中.先进行网络资源异步访问,然后将回调函数 Invoke到UI线程中进行UI处理. 这样的 ...

  3. C#操作Control异步工具类

    /// <summary> /// 异步工具类 /// </summary> public class TaskTools { /// <summary> /// ...

  4. Java8 异步编排类CompletableFuture

    为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/ShiJiaqi. https://www.cnblogs.com/shijiaqi1066/p/8758206 ...

  5. 异步IO类

    也学习多线程一段时间了,也写了几个简单实用的功能类,也意思到细节的处理的重要性,现在就让我们来写一个稍稍更有用的异步IO的类. 本来想参考Java NIO 中的类,Java NIO作为新io包,本身提 ...

  6. ReactNative: 使用AsyncStorage异步存储类

    一.简介 AsyncStorage是一个简单的具有异步特性可持久化的键值对key-value的存储系统.它对整个APP而言,是一个全局的存储空间,可以用来替代H5中提供的window属性LocalSt ...

  7. AsyncTask异步任务类使用学习

    new MyAsyncTask() .execute("http://pic.baike.soso.com/p/20120716/bki-20120716095331-640956396.j ...

  8. Task Class .net4.0异步编程类

    文章:Task Class 地址:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.task?view=netfra ...

  9. Apex 的 Trigger 类简介

    Apex Triggers Apex 触发器(Apex Triggers)是一种特殊的 Apex 类.它的主要作用是在一条记录被插入.修改.删除之前或之后自动执行一系列的操作.每一个 Trigger ...

随机推荐

  1. 如何正确的使用Ubuntu以及安装常用的渗透工具集.

    文章来源i春秋 入坑Ubuntu半年多了  记得一开始学的时候基本一星期重装三四次=-= 尴尬了 觉得自己差不多可以的时候 就吧Windows10干掉了 c盘装Ubuntu 专心学习.   这里主要来 ...

  2. OkHttp 入门篇

    OkHttp是一个HTTP & HTTP2的客户端,能够用来进行Android 和 Java 开发. HTTP是现代应用的最基本的网络环境.让你的HTTP更加有效的工作能够让你的东西加载更快而 ...

  3. OO第一单元自白

    Homework 1  简单多项式导函数 对于初次接触的OO,第一次作业已经可以体会到其与面向过程的C语言之间的差别. 我的想法是,建立了Multinomial和Monomial 两个类,分别能够实现 ...

  4. Linux中vim文本编辑器的介绍和使用方法

    vim主要模式介绍,vim命令模式. 确保系统已经安装了VIM工具 [root@panda ~]# rpm -qf `which vim` [root@panda ~]# rpm -qf `which ...

  5. Liferay7 BPM门户开发之7: Activiti中的重要概念和主要数据库结构

    流程的人员参与角色: Assignee :签收者(即待办人) Candidate:候选人 Owner:拥有者 Starter:启动者 participant:参与者,包含查阅 流程变量的类型: Str ...

  6. 理解Array.prototype.fill和Array.from

    之所以将这两个方法放在一起说,是因为经常写这样的代码: Array.from({length: 5}).fill(0),看起来很简洁,但是踩到坑之后才发现自己对这两个方法实在是不求甚解. Array. ...

  7. MVC5笔记

    创建一个MVC网站后,我们可以在/app_strat/routeConfig.cs中来查看集中控制路的方法,RegisterRoutes方法(注册路由),我们改一下,删除默认的RegisterRout ...

  8. 大牛是怎么思考设计MySQL优化方案

    在进行MySQL的优化之前,必须要了解的就是MySQL的查询过程,很多查询优化工作实际上就是遵循一些原则,让MySQL的优化器能够按照预想的合理方式运行而已. 1.优化的哲学 注:优化有风险,涉足需谨 ...

  9. lua脚本在游戏中的应用

    为什么要在游戏中使用脚本语言? 要解释这个问题首先我们先来了解一下脚本语言的特性: 学习门槛低,快速上手 开发成本低,可维护性强 动态语言,灵活性高 相对于C/C++这类高复杂性.高风险的编译型语言来 ...

  10. centos适用的国内yum源:网易、搜狐

    默认的yum源是centos官网的,速度慢是不用说了.所以使用yum安装东西之前需要把yum源改为国内的.参考 http://mirrors.163.com/.help/centos.html 和 h ...