Salesforce Apex 入门教程
⚡ 智能摘要
Apex 编程为 Salesforce 开发人员提供了一种面向对象、强类型的语言,用于向 CRM 添加自定义业务逻辑。它支持类、触发器和批处理作业,而治理限制则确保了共享多租户平台的稳定性。

Salesforce 中的 Apex 是什么?
顶点 Apex 是一种面向对象、强类型的编程语言,由 Salesforce 开发,用于构建软件即服务 (SaaS) 和客户关系管理 (CRM) 应用程序。Apex 通过提供后端数据库支持和客户端-服务器接口,帮助开发人员创建第三方 SaaS 应用程序,并将业务逻辑添加到系统事件中。
Apex 帮助开发人员向系统事件(例如按钮点击、相关记录更新和 Visualforce 页面)添加业务逻辑。Apex 的语法与此类似。 Java. 注册 Salesforce 了解客户关系管理系统(CRM)的工作原理。
Apex编程语言的特点
以下是 Salesforce Apex 的重要功能:
- Apex 是一种不区分大小写的语言。
- 您可以使用 Apex 对 sObject 记录执行 DML 操作,例如 INSERT、UPDATE、UPSERT 和 DELETE。
- 您可以使用 Apex 中的 SOQL(Salesforce 对象查询语言)和 SOSL(Salesforce 对象搜索语言)查询 sObject 记录。
- 允许您创建一个 单元测试 并执行它以验证 代码覆盖率 以及 Apex 代码的效率。
- Apex 在多租户环境中执行,并且 Salesforce Salesforce 已设定权限限制,防止用户独占共享资源。任何超出 Salesforce 权限限制的代码都会失败,并显示错误信息。
- Salesforce 对象可以用作 Apex 中的数据类型。例如:
Account acc = new Account();
这里,Account 是一个标准的 Salesforce 对象。
- Apex 会随着 Salesforce 的每次发布而自动升级。
开发者何时应该选择 Apex?
只有当业务场景过于复杂,无法使用 Salesforce 提供的预置点击式功能来实现时,才应该编写 Apex 代码。
以下是一些需要编写 Apex 代码的场景:
- 创建将 Salesforce 与其他应用程序集成的 Web 服务。
- 对 sObject 实现自定义验证。
- 在执行 DML 操作时执行自定义 Apex 逻辑。
- 实现无法使用现有声明式自动化工具(例如 Flow)构建的功能。
- 建立 电邮服务 处理入站电子邮件的内容、邮件头和附件。
一旦你了解了 Apex 何时是正确的选择,下一步就是了解保存代码后会发生什么。
Apex 的工作结构
以下是 Apex 代码的操作流程:
- 开发者操作: 当开发者将代码保存到平台时,所有 Apex 代码都会被编译成 Apex 运行时解释器可以理解的一组指令,然后这些指令会作为元数据保存到平台上。
- 最终用户操作: 当用户事件执行 Apex 代码时,平台服务器会从元数据中检索已编译的指令,并通过 Apex 解释器运行这些指令,然后再返回结果。
下图展示了开发人员和最终用户如何与 Lightning 平台应用程序服务器进行交互:
Apex 开发环境
Apex 代码既可以在 Salesforce 的沙盒环境中开发,也可以在 Salesforce 的开发者版本组织中开发。
最佳实践是在沙箱环境中开发代码,然后将其部署到生产环境,如下图所示:
Apex 代码开发工具:以下是 Salesforce 所有版本中可用于开发 Apex 代码的三种工具:
- 开发者控制台
- Visual Studio Code 使用 Salesforce 扩展包(它取代了已停用的 Force.com IDE)
- Code Salesforce 设置用户界面中的编辑器
环境准备就绪后,让我们来看看语言本身的组成部分,首先是数据类型。
Apex 中的数据类型
以下是 Apex 支持的数据类型:
原始
整数, DoubleLong、Date、Datetime、Decimal、Time、Blob、String、ID 和 Boolean 都被视为基本数据类型。所有基本数据类型都按值传递,而不是按引用传递。
系列
Apex 中提供了三种类型的集合:
- 列表: 它是基于索引的原始对象、sObject、集合或Apex对象的有序集合。
- 设置: 一个无序的、不包含重复元素的唯一元素集合。
- 地图: 它是一组映射到单个值的唯一键的集合,这些值可以是原始值、sObject、集合或 Apex 对象。
对象
这是 Salesforce 中一种特殊的数据类型。它类似于 SQL 并且包含类似于 SQL 中列的字段。
枚举
枚举是绝对值tract 数据类型,用于存储一组有限的指定标识符中的一个值。
类、对象和接口
用户自定义的 Apex 类和接口也可以用作数据类型。对象指的是 Apex 支持的任何数据类型的实例。
Apex 语法
变量声明
由于 Apex 是一种强类型语言,因此在 Apex 中必须声明具有数据类型的变量。
例如:
Contact con = new Contact();
这里声明的变量 con 的数据类型为 Contact。
SOQL 查询
SOQL 代表 Salesforce 对象查询语言。SOQL 用于从 Salesforce 数据库中获取 sObject 记录。例如:
Account acc = [SELECT Id, Name FROM Account LIMIT 1];
上述查询从 Salesforce 数据库中获取帐户记录。
循环语句
循环语句用于遍历列表中的记录。循环次数等于列表中的记录数。例如:
List<Account> listOfAccounts = [SELECT Id, Name FROM Account LIMIT 100];
// iteration over the list of accounts
for(Account acc : listOfAccounts){
//your logic
}
在上面的代码片段中,listOfAccounts 是一个 List 数据类型的变量。
流程控制语句
当您希望根据特定条件执行某些代码行时,流程控制语句非常有用。
例如:
List<Account> listOfAccounts = [SELECT Id, Name FROM Account LIMIT 100];
// execute the logic if the size of the account list is greater than zero
if(listOfAccounts.size() > 0){
//your logic
}
以上代码片段从数据库中查询帐户记录并检查列表大小。
DML声明
DML 代表数据操作语言。DML 语句用于操作 Salesforce 数据库中的数据。例如:
Account acc = new Account(Name = 'Test Account'); insert acc; //DML statement to create account record.
Apex 访问权限规范
以下是 Apex 支持的访问控制符:
公共
此访问说明符允许 Apex 在命名空间内访问类、方法或变量。
私做
此访问说明符允许对类、方法或变量进行局部访问,即在其定义所在的代码段内使用。所有未定义访问说明符的方法和变量都使用默认的访问说明符 private。
保护
此访问说明符允许定义 Apex 类中的任何内部类访问方法或变量。
全球
此访问修饰符允许 Apex 在命名空间内外访问类、方法或变量。最佳实践是,除非必要,否则不要使用 global 关键字。
Apex 中的关键字
通过分享
如果类定义中使用此关键字,则会强制执行适用于当前用户的所有共享规则。如果缺少此关键字,则代码将在系统上下文中执行。
例如:
public with sharing class MyApexClass{
// sharing rules enforced when code in this class executes
}
无需共享
如果用此关键字定义类,则不会强制执行适用于当前用户的共享规则。
例如:
public without sharing class MyApexClass{
// sharing rules are not enforced when code in this class executes
}
静止
使用 static 关键字定义的变量或方法只初始化一次,并与类关联。静态变量和方法可以直接通过类名调用,而无需创建类的实例。
最终的
使用 final 关键字定义的常量或方法不能被重写。例如:
public class myCls {
static final Integer INT_CONST = 10;
}
如果您尝试覆盖此 INT_CONST 变量的值,则会收到异常 – System.FinalException:最终变量已被初始化。
回程
此关键字从方法返回一个值。例如:
public String getName() {
return 'Test';
}
空
它定义了一个空常量,并且可以将其赋值给一个变量。例如:
Boolean b = null;
虚拟
如果一个类是用 virtual 关键字定义的,那么它可以被扩展和重写。
抽象
如果一个类是用绝对值定义的trac对于关键字 t,它必须至少包含一个带有关键字 abs 的方法。tract,并且该方法应该只有签名。
例如:
public abstract class MyAbstractClass {
abstract Integer myAbstractMethod1();
}
Apex 字符串
字符串是一组字符,没有字符数限制。例如:
String name = 'Test';
Salesforce 中的 String 类提供了几个内置方法。以下是一些常用方法:
缩写(最大宽度)
此方法将字符串截断为指定长度,如果给定字符串的长度大于指定长度,则返回截断后的字符串;否则,返回原始字符串。如果 maxWidth 变量的值小于 4,则此方法会抛出运行时异常——System.StringException:最小缩写宽度为 4。
例如:
String s = 'Hello World';
String s2 = s.abbreviate(8);
System.debug('s2: ' + s2); //Hello...
大写()
此方法将字符串的首字母转换为标题大小写并返回。
例如:
String s = 'hello';
String s2 = s.capitalize();
System.assertEquals('Hello', s2);
包含(子字符串)
如果调用该方法的字符串包含指定的子字符串,则该方法返回 true。
String name1 = 'test1';
String name2 = 'test';
Boolean flag = name1.contains(name2);
System.debug('flag:: ' + flag); //true
等于(stringOrId)
如果传递的参数不为空,并且表示与调用该方法的字符串相同的二进制字符序列,则此方法返回 true。
比较 ID 值时,ID 的长度可能不相等。例如,如果将表示 15 个字符 ID 的字符串与表示 18 个字符 ID 的对象进行比较,此方法仍然返回 true。例如:
String stringValue15 = '001D000000Ju1zH';
Id idValue18 = '001D000000Ju1zHIAR';
Boolean result = stringValue15.equals(idValue18);
System.debug('result: ' + result); //true
在上面的例子中,equals 方法将一个 15 个字符的对象 ID 与一个 18 个字符的对象 ID 进行比较,如果两个 ID 表示相同的二进制序列,则返回 true。
使用此方法进行区分大小写的比较。
escapeSingleQuotes(stringToEscape)
此方法会在字符串中的每个单引号前添加转义字符 (\) 并返回结果。此方法可防止在创建动态 SOQL 查询时发生 SOQL 注入攻击。它确保所有单引号都被视为包含字符串,而不是数据库命令。
例如:
String s = 'Hello \'Tom\''; String escapedStr = String.escapeSingleQuotes(s); System.debug(escapedStr); // Outputs Hello \'Tom\'
删除(子字符串)
此方法从调用该方法的字符串中删除所有指定的子字符串,并返回结果字符串。
例如:
String s1 = 'Salesforce and force.com';
String s2 = s1.remove('force');
System.debug('s2: ' + s2); // 'Sales and .com'
子字符串(起始索引)
此方法返回一个子字符串,该子字符串从 startIndex 处的字符开始,一直延伸到字符串的末尾。
例如:
String s1 = 'hamburger';
String s2 = s1.substring(3);
System.debug('s2: ' + s2); //burger
撤销()
此方法会将字符串中的所有字符反转并返回。例如:
String s = 'Hello';
String s2 = s.reverse();
System.debug('s2:::: ' + s2); // olleH
修剪()
此方法会删除字符串开头和结尾的所有空格并返回该字符串。
值(要转换)
此方法返回传入对象的字符串表示形式。
当您开始将逻辑打包成可重用的单元时,字符串、类和关键字就会结合在一起,而这正是 Apex 类的用途。
巅峰级
Apex 类是创建对象的蓝图或模板。对象是类的实例。
在 Salesforce 中创建 Apex 类有三种方法:
- 开发者控制台
- Visual Studio Code 使用 Salesforce 扩展包
- 设置中的 Apex 类详细信息页面
在 Apex 中,您可以定义一个外部类(也称为顶级类),也可以在外部类中定义类(称为内部类)。
在声明外部类时,必须使用 global 或 public 等访问修饰符。
声明内部类时,无需使用访问修饰符。
Apex 类是使用 class 关键字后跟类名来定义的。
extends 关键字用于在 Apex 类中扩展现有类,而 implements 关键字用于在 Apex 类中实现接口。
Salesforce Apex 不支持多重继承;一个 Apex 类只能继承一个现有的 Apex 类,但可以实现多个接口。
Apex 类可以包含用户自定义构造函数;如果不存在用户自定义构造函数,则使用默认构造函数。构造函数中的代码在创建类实例时执行。
Apex 类的语法:
public class myApexClass{
// variable declaration
//constructor
public myApexClass(){
}
//methods declaration
}
new 关键字用于创建 Apex 类的实例。以下是创建 Apex 类实例的语法:
myApexClass obj = new myApexClass();
Apex Getter 和 Setter
Apex 属性类似于 Apex 变量。Apex 属性必须包含 getter 和 setter。它们可用于在访问或更改属性值之前执行代码。getter 访问器中的代码在读取属性值时执行。setter 访问器中的代码在更改属性值时执行。仅包含 getter 访问器的属性被视为只读属性,仅包含 setter 访问器的属性被视为只写属性,同时包含 getter 和 setter 访问器的属性被视为读写属性。Apex 属性的语法:
public class myApexClass {
// Property declaration
access_modifier return_type property_name {
get {
//code
}
set{
//code
}
}
}
这里,access_modifier 是属性的访问修饰符,return_type 是属性的数据类型,property_name 是属性的名称。
以下是一个同时具有 get 和 set 访问器的 Apex 属性示例:
public class myApex{
public String name{
get{ return name; }
set{ name = value; }
}
}
这里,属性名称为 name,它是一个公共属性,并且它返回一个字符串数据类型。
get 和 set 方法块中并非必须包含代码。这些方法块可以留空,以定义一个自动属性。例如:
public double MyReadWriteProp{ get; set; }
获取和设置访问器也可以定义自己的访问修饰符。如果访问器定义了修饰符,则会覆盖属性的访问修饰符。例如:
public String name{private get; set;}// name is private for read and public for write.
类定义了可重用的逻辑,您可以显式调用这些逻辑。触发器(稍后介绍)会在记录更改时自动运行。
顶点触发器
Apex 触发器允许您在执行 DML 操作之前和之后执行自定义 Apex 代码。
Apex 支持以下两种类型的触发器:
触发条件之前: 这些触发器用于在将记录保存到数据库之前验证和更新字段的值。
触发条件之后: 这些触发器用于访问系统在记录提交到数据库后设置的字段值(例如记录 ID 和 LastModifiedDate 字段)。这些字段值可用于修改其他记录。触发器触发后创建的记录为只读。
编写批量触发器是最佳实践。批量触发器可以一次处理单个记录,也可以一次处理多个记录。
Apex触发器的语法:
trigger TriggerName on ObjectName (trigger_events) {
//Code_block
}
这里,TriggerName 是触发器的名称,ObjectName 是写入触发器的对象的名称,trigger_events 是以逗号分隔的事件列表。
Apex 触发器支持以下事件:插入前、更新前、删除前、插入后、更新后、删除后、取消删除后。
静态关键字不能用于 Apex 触发器。所有适用于内部类的关键字都可以用于 Apex 触发器。
每个触发器都定义了一些隐式变量,用于返回运行时上下文。这些变量定义在 System.Trigger 类中,称为上下文变量。以下两张屏幕截图列出了 Apex 触发器支持的上下文变量:
以下是Apex触发器中上下文变量需要考虑的因素:
- 请勿在 DML 操作中使用 trigger.new 和 trigger.old。
- 触发器.new 无法删除。
- 在触发器之后,trigger.new 是只读的。
- Trigger.new 只能用于在触发器之前更改同一对象上字段的值。
以下两张截图列出了不同触发事件中特定操作的注意事项:
触发器处理实时逻辑,但有些任务规模太大,单个事务无法完成。这时就需要用到批量 Apex 了。
Apex 中的批次类
Salesforce 中的批处理类用于处理大量记录,这些记录如果按常规方式处理将会超出 Apex 的治理限制。批处理类以异步方式执行代码。
以下是批量教学的优势:
- 批处理类分块处理数据,如果其中一个块处理失败,则不会回滚已成功处理的数据块。
- 批处理类中的每个数据块都会使用一组新的控制限制进行处理,从而确保代码在控制执行限制内执行。
要将 Apex 类用作批处理类,必须实现 Database.Batchable 接口。它提供了三个批处理类必须实现的方法:
1. 开始()
此方法生成接口方法 execute 要处理的记录或对象范围。它在批处理执行期间仅调用一次。此方法返回 Database.QueryLocator 对象或 Iterable 对象。使用 Database.QueryLocator 对象执行 SOQL 查询可检索 50 万条记录,但使用 Iterable 对象则只能检索 50,000 万条记录。Iterable 对象用于为批处理类生成复杂的范围。
start 方法的语法:
global (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {}
2. 执行()
此方法用于处理每个数据块。执行方法会针对每个记录块调用。默认的执行批次大小为 200 条记录。执行方法接受两个参数:
对 Database.BatchableContext 对象的引用,
sObject 列表,例如 List或者参数化类型的列表。execute 方法的语法:
global void execute(Database.BatchableContext bc, List<P> records){}
3.完成()
finish 方法在批处理类执行期间调用一次。可以在 finish 方法中执行后处理操作,例如:发送确认邮件。此方法在所有批处理完成后调用。finish 方法的语法:
global void finish(Database.BatchableContext bc){}
数据库.BatchableContext 对象
Database.Batchable 接口的每个方法都引用了一个 Database.BatchableContext 对象。
此对象用于 track 批处理作业的进度。
以下是 BatchableContext 提供的实例方法:
- getChildJobId(): 此方法返回当前正在处理的批处理作业的 ID。
- getJobId():此方法返回批处理作业的ID。
以下是批处理类的语法:
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
}
}
Database.executeBatch 方法
Database.executeBatch 方法用于执行批处理类。
此方法接受两个参数:要处理的批处理类的实例,以及一个可选的作用域参数,用于指定批处理大小。如果未指定,则使用默认值 200。
Database.executeBatch 的语法:
Database.executeBatch(myBatchObject, scope)
执行名为 MyBatchClass 的批处理类:
MyBatchClass myBatchObject = new MyBatchClass(); Id batchId = Database.executeBatch(myBatchObject, 100);
数据库.有状态
默认情况下,批处理类是无状态的。每次调用 execute 方法时,都会收到对象的新副本,并且该类的所有变量都会被初始化。
实现 Database.Stateful 是为了使批处理类成为有状态的。
如果你的批处理类实现了 数据库.有状态接口,所有实例变量都会保留其值,但静态变量会在事务之间重置。
异步 Apex Beyond Batch
批量 Apex 只是异步运行代码的几种方式之一。Salesforce 提供了四种异步选项,选择合适的选项取决于您处理的数据量以及作业是否需要链式执行或定时执行。
- 未来方法: 使用 @future 进行注释,这些最适合简单的、即发即弃的操作,例如调用外部 Web 服务。
- 可排队 Apex: 实现了 Queueable 接口,接受复杂对象类型,返回作业 ID,并支持将一个作业链接到另一个作业。
- 批量顶点: 如上所述,分块处理海量数据。
- 预定顶点: 实现 Schedulable 接口,以便类在特定时间运行,例如夜间清理作业。
| 类型 | 最适合 | 关键能力 |
|---|---|---|
| 未来方法 | 简单标注 | 发射后不管 |
| 可排队 Apex | 顺序处理 | 工作链和监控 |
| 批量 Apex | 数百万条记录 | 分块处理 |
| 预定顶点 | 重复性工作 | 基于 Cron 的定时 |
无论你选择哪种执行模型,每笔交易仍然会根据下面列出的平台范围的限制进行衡量。
Apex 调控器限制
Apex 治理限制是由 Apex 运行时引擎强制执行的限制,旨在确保任何失控的 Apex 代码和进程不会独占共享资源,也不会干扰多租户环境中其他用户的处理。这些限制会针对每个 Apex 事务进行验证。以下是 Salesforce 为每个 Apex 事务定义的治理限制:
| 描述 | 限制 |
|---|---|
| 可以在同步事务中完成的 SOQL 查询 | 100 |
| 可以在异步事务中执行的 SOQL 查询 | 200 |
| 可以通过 SOQL 查询检索的记录 | 50,000 |
| 可以通过 Database.getQueryLocator 检索的记录 | 10,000 |
| 可以在 Apex 事务中执行的 SOSL 查询 | 20 |
| 可以通过 SOSL 查询检索的记录 | 2,000 |
| 可以在 Apex 事务中执行的 DML 语句 | 150 |
| 可以通过 DML 语句、Approval.process 或 database.emptyRecycleBin 处理的记录 | 10,000 |
| 可以在 Apex 事务中执行的调用 | 100 |
| Apex 事务中所有调用操作的累计超时限制 | 120秒 |
| 使用 System.enqueueJob 添加到队列中的 Apex 作业数量有限制 | 50 |
| 每笔 Apex 交易的执行时间限制 | 10分钟 |
| Apex 类和触发器中可使用的字符数限制 | 1百万 |
| 同步事务的 CPU 时间限制 | 10,000毫秒 |
| 异步事务的 CPU 时间限制 | 60,000毫秒 |
| 总堆大小 | 6 MB(同步)/ 12 MB(异步) |
如何在 Apex 中编写测试类
Salesforce 要求至少 75% 的 Apex 代码必须经过单元测试才能部署到生产环境,并且每个触发器都必须有一定的测试覆盖率。因此,编写测试类是 Apex 的核心技能,而非可有可无的附加技能。
测试类使用 `@isTest` 注解,每个测试方法也使用 `@isTest` 注解。测试方法不会向数据库提交数据,也不会访问大多数现有的组织数据,因此每个测试都会创建自己的记录。`Test.startTest()` 和 `Test.stopTest()` 方法会为被测代码设置一组新的限制,断言会验证逻辑是否按预期运行。
以下是一个简单的账户创建逻辑测试类:
@isTest
private class AccountHandlerTest {
@isTest
static void testCreateAccount() {
Account acc = new Account(Name = 'Test Account');
Test.startTest();
insert acc;
Test.stopTest();
Account result = [SELECT Id, Name FROM Account WHERE Id = :acc.Id];
System.assertEquals('Test Account', result.Name);
}
}
编写测试时请遵循以下准则:
- 使用 @testSetup 方法一次性创建类中所有测试方法的共享测试数据。
- 测试批量行为时,请插入 200 条记录,而不是仅仅一条。
- 涵盖正面、负面和受限用户场景。
- 务必包含有意义的 System.assert 语句;没有断言的覆盖率说明不了任何问题。
Apex最佳实践
初学者编写的 Apex 代码通常可以处理单个记录,但在实际批量操作中却会失败。以下实践可确保您的代码符合规范限制,并且更易于维护:
- 批量处理所有物品: 编写处理记录集合的逻辑,因为触发器一次最多可以接收 200 条记录。
- 避免在循环中使用 SOQL 和 DML: 在循环之前进行查询,将更改收集到一个列表中,并在循环之后执行一条 DML 语句。
- 每个对象一个触发器: 保持触发器不包含逻辑,并将工作委托给处理程序类,这样执行顺序就可预测了。
- 用于分享: 除非有明确的理由需要在系统上下文中运行,否则应强制执行记录级安全。
- 避免将ID硬编码到代码中: 沙箱和生产环境的记录 ID 不同,因此需要查询记录 ID 或改用自定义元数据。
- 代码中的监控限制: Limits 类的方法,例如 Limits.getQueries(),允许您在运行时检查资源消耗情况。
👍 提示: 部署前,先在沙箱环境中用 200 条记录运行代码。大多数限速问题只会在数据量巨大时出现,及早发现这些问题远比在生产环境中调试成本低得多。






