Apex 基础与数据库

掌握 Apex 编程语言基础:数据类型与集合、sObject 记录访问、DML 数据操作、Database 方法、SOQL 查询与 SOSL 文本搜索。...

📅 2026/10/4 ✍️ ponybai 🏷️ salesforce, developer, apex, headless

学习目标

slide_2

完成本模块后,你将能够:

  • 描述 Apex 编程语言的关键特性。
  • 保存一个 Apex 类,并用匿名 Apex(Anonymous Apex)调用其方法。
  • 使用开发者控制台(Developer Console)查看调试日志(debug logs)。

开始之前:本模块虽然涵盖编程语言的基础知识,但会很快深入。如果这是你第一次接触 Apex,强烈建议先完成 Quick Start: Apex 项目,再回来继续。

什么是 Apex?

slide_3

Apex 是一种使用类 Java(Java-like)语法的编程语言,其行为类似数据库存储过程(stored procedures)。Apex 让开发者能够为系统事件添加业务逻辑,例如按钮点击、关联记录的更新以及 Visualforce 页面。

作为一种语言,Apex 具有以下特点:

  • 托管(Hosted)——Apex 在服务器端(即 Salesforce 平台)保存、编译和执行。
  • 面向对象(Object-oriented)——支持类、接口和继承。
  • 强类型(Strongly-typed)——在编译时校验对对象的引用。
  • 多租户感知(Multitenant-aware)——因为运行在多租户平台上,Apex 通过强制执行限制来防止失控代码,避免代码独占共享资源。
  • 与数据库集成(Integrated with the database)——可以直接访问和操作记录及其字段,并提供语句和查询语言。
  • 以数据为中心(Data-focused)——提供对数据库的事务性访问,支持回滚操作。
  • 易于使用(Easy to use)——基于熟悉的 Java 惯用语法。
  • 易于测试(Easy to test)——内置单元测试的创建、执行和代码覆盖率支持。
  • 版本化(Versioned)——自定义 Apex 代码可以针对不同版本的 API 保存。

Apex 语言要点

slide_4

与其他面向对象编程语言一样,Apex 支持以下语言结构:

  • 类、接口、属性和集合(List、Map、Set)。
  • 对象和数组表示法。
  • 表达式、变量和常量。
  • 条件语句(if-then-else)和控制流语句(for 循环、while 循环)。

与其他面向对象语言不同,Apex 还支持:

  • 云开发——Apex 在云端存储、编译和执行。
  • 触发器(Trigger),类似数据库系统中的触发器。
  • 数据库语句,可直接进行数据库调用,并用查询语言查询和搜索数据。
  • 事务和回滚。
  • global 访问修饰符,比 public 更宽松,允许跨命名空间和应用程序访问。
  • 自定义代码的版本化。

此外,Apex 是一种不区分大小写(case-insensitive)的语言。

开发工具

slide_5

你可以使用 Visual Studio Code 的 Salesforce Extensions 在本地计算机上编写和调试 Apex。

也可以直接在浏览器中使用 Salesforce 用户界面编写 Apex 并访问调试信息——从快速访问菜单(quick access menu)打开开发者控制台(Developer Console)。

数据类型概览

slide_6

Apex 支持多种数据类型,其中包括 Salesforce 特有的数据类型——sObject。

Apex 支持以下数据类型:

  • 原始类型(Primitive),如 Integer、Double、Long、Date、Datetime、String、ID、Boolean 等。
  • sObject,可以是泛型 sObject,也可以是特定 sObject(如 Account、Contact 或 MyCustomObject__c)。
  • 集合(Collection),包括:由原始类型、sObject、用户自定义对象、Apex 类创建的对象或集合组成的 Set;键和值可为任意数据类型的 Map;以及有类型的值列表(typed list),也称 enum。
  • 用户自定义的 Apex 类。
  • 系统提供的 Apex 类。

Apex 集合:List

slide_7

List 用于保存同一类型数据的有序集合。

以下两种声明是等价的,colors 变量既可以用 List 语法声明,也可以用数组表示法声明:

List<String> colors = new List<String>();
String[] colors = new String[5];

使用 List.add() 方法可以按需扩展集合、添加新元素;可以用方括号数组表示法按索引引用已有元素,但不能用它来添加更多元素。

List 元素可以通过方括号内的索引读取(数组表示法),也可以用 get() 方法读取,还可以对 List 元素进行遍历(iterate)。

进阶(Beyond the Basics):Apex 还支持另外两种集合类型:Set 和 Map,可在 Apex 开发指南的 Collections 章节了解更多。

保存一个 Apex 类

slide_8

Apex 类的一大好处是代码复用——类方法可以被触发器和其他类调用。下面通过保存一个示例类、用该类发送邮件并查看调试日志来演示。

EmailManager 类有一个公有方法 sendMail()(发送邮件,使用 Apex 类库中内置的 Messaging 方法),还有一个私有辅助方法 inspectResults()(只能在类内部调用,用于检查邮件发送结果)。

public with sharing class EmailManager {
    // Public method
    public void sendMail(String address, String subject, String body) {
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        String[] toAddresses = new String[] {address};
        mail.setToAddresses(toAddresses);
        mail.setSubject(subject);
        mail.setPlainTextBody(body);
        Messaging.SendEmailResult[] results = Messaging.sendEmail(
                                 new Messaging.SingleEmailMessage[] { mail });
        inspectResults(results);
    }
    private static Boolean inspectResults(Messaging.SendEmailResult[] results) {
        Boolean sendResult = true;
        for (Messaging.SendEmailResult res : results) {
            if (res.isSuccess()) {
                System.debug('Email sent successfully');
            } else {
                sendResult = false;
                System.debug('The following errors occurred: ' + res.getErrors());
            }
        }
        return sendResult;
    }
}

保存类的步骤:在开发者控制台中点击 File | New | Apex Class,输入类名 EmailManager 并点击 OK,替换默认类体,然后点击 File | Save 保存。注意:如果代码语法不正确,Problems 选项卡会显示错误,可根据错误详情修正。保存时 Salesforce 会编译该类。

调用方法并查看调试日志

slide_9

使用匿名 Apex(Anonymous Apex)可以在不保存类的情况下即时运行代码,是快速测试功能的好方法。匿名 Apex 执行时也会像其他 Apex 执行一样生成调试日志。

调用公有方法发送邮件的步骤:在开发者控制台点击 Debug | Open Execute Anonymous Window,输入以下代码(将邮箱替换成你的地址),然后点击 Execute:

EmailManager em = new EmailManager();
em.sendMail('Your email address', 'Trailhead Tutorial', '123 body');

调试日志对排查代码问题很有用。Apex 方法执行时,调用会被记录在调试日志中。你也可以通过 System.debug() 方法写入自己的调试消息——例如 inspectResults() 辅助方法就通过 System.debug() 记录邮件发送成功与否。

查看日志的步骤:在开发者控制台点击 Logs 选项卡,双击列表中最新的日志,选择 Debug Only 过滤,只显示 System.debug() 语句对应的日志行。

调用静态方法:由于 sendMail() 不访问类的成员变量,无需作为实例方法。在声明中加入 static 关键字即可改为静态方法,直接在类名上调用(无需实例):

public static void sendMail(String address, String subject, String body) { ... }
EmailManager.sendMail('Your email address', 'Trailhead Tutorial', '123 body');

使用 sObject

slide_10

本单元学习如何用 sObject 在 Apex 中表示 Salesforce 记录——创建特定类型和泛型 sObject 变量、访问字段,以及在类型之间进行转换。

创建与访问 sObject

slide_11

由于 Apex 与数据库紧密集成,你可以直接从 Apex 访问 Salesforce 记录及其字段。Salesforce 中的每条记录在 Apex 中原生地表示为 sObject。例如,Acme 账户记录对应 Apex 中的 Account sObject,其字段可以直接在 sObject 上读写。

标准对象和自定义对象在 Salesforce 中的记录映射到 Apex 中对应的 sObject 类型。常见标准对象的 sObject 类型名有 Account、Contact、Lead、Opportunity。如果添加了自定义对象,在 Apex 中使用其 API 名称,例如自定义对象 Merchandise 对应 Apex 中的 Merchandise__c sObject。

创建 sObject 需要声明变量并为其分配一个 sObject 实例,变量的数据类型就是 sObject 类型:

Account acct = new Account(Name='Acme', Phone='(415)555-1212', NumberOfEmployees=100);

sObject 与字段名:Apex 使用唯一的 API 名称引用标准或自定义 sObject 及其字段。API 名称可能与标签不同,例如 Employees 字段的 API 名称是 NumberOfEmployees。对于自定义对象和自定义字段,API 名称总是以 __c 结尾;自定义关系字段以 __r 结尾(如 Items__r)。标签中的空格在 API 名称中替换为下划线。

添加字段有两种方式:在构造函数中以 name-value 对指定(最快),或使用点表示法(dot notation)。

泛型 sObject 与类型转换

slide_12

通常使用特定 sObject 类型(如 Account 或 Book__c)处理记录;但当不知道方法处理的 sObject 类型时,可以使用泛型 sObject 数据类型。用泛型 sObject 声明的变量可以引用任何 Salesforce 记录(标准或自定义对象记录)。

泛型 sObject 的字段只能通过 put() 和 get() 方法访问,无法使用点表示法:

sObject genericRecord = new Account();
genericRecord.put('Name', 'Acme');
String name = (String) genericRecord.get('Name');

与之相对,用特定 sObject 类型声明的变量只能引用同类型的 Salesforce 记录。

处理泛型 sObject 时,有时需要将其转换为特定 sObject 类型——好处之一是可以使用点表示法访问字段。由于 sObject 是所有特定 sObject 类型的父类型,可以将泛型 sObject 转换为特定类型:

Account acct = (Account) genericRecord;
acct.Name = 'Acme';  // 现在可以使用点表示法

使用 DML 操作记录

slide_13

本单元学习 DML(数据操作语言 Data Manipulation Language)——用于创建、更新、删除和 upsert Salesforce 记录的 Apex 语句,涵盖 DML 语句和 Database 方法。

DML 语句:插入与更新

slide_14

通过数据操作语言 DML 可以在 Salesforce 中创建和修改记录。Apex 是以数据为中心的语言,直接访问 Salesforce 数据。可用的 DML 语句有:insert、update、upsert、delete、undelete 以及 merge(仅用于 Lead、Contact、Case、Account)。每条 DML 语句接受单个 sObject 或 sObject 列表,对列表操作更高效。

Account acct = new Account(Name='Acme');
insert acct;   // acct.Id 现在已填充新记录 ID
acct.Phone = '(415)555-1212';
update acct;

ID 字段自动分配:插入记录时,系统会为每条记录分配一个 ID,并自动回填到作为 DML 调用参数的 sObject 变量上,因此可以复用该变量进行后续 DML 操作。

批量 DML(推荐):对 sObject 列表执行 DML 有助于避免触及 governor limits(每个 Apex 事务最多 150 条 DML 语句)。对列表执行一次 DML 算作一条语句:

List<Contact> conList = new List<Contact> { ... };
insert conList;   // 一次 DML 插入所有联系人

Upsert 记录

slide_15

如果列表中同时包含新记录和已有记录,可以用 upsert 语句一次性处理所有记录的插入和更新,避免创建重复记录并节省时间。

upsert 通过比较某个字段的值来匹配现有记录。如果不指定字段,则使用 sObject 的 ID 匹配;也可以指定字段用于匹配——对于自定义对象,指定标记为外部 ID 的字段;对于标准对象,指定 idLookup 属性为 true 的字段(例如 Contact 或 User 的 Email 字段)。

Upsert 根据主键(ID)、idLookup 字段或外部 ID 字段判断是新建还是更新:键未匹配则新建记录;键匹配一次则更新已有记录;键匹配多次则报错且不插入也不更新。

Contact josh = new Contact(FirstName='Josh', LastName='Kaplan', Department='Finance');
insert josh;
josh.Description = 'Josh record has been updated by the upsert operation.';
Contact kathy = new Contact(FirstName='Kathy', LastName='Brown', Department='Technology');
List<Contact> contacts = new List<Contact> { josh, kathy };
upsert contacts;   // Josh 被更新,Kathy 被新建

也可以指定用于匹配的字段,例如用 Contact 的 Email 字段(upsert jane2 Contact.fields.Email;)。若此处用 insert 代替 upsert,会插入重复的 Jane Smith 联系人。

删除记录与 DML 异常

slide_16

使用 delete 语句删除已持久化的记录。删除的记录不会立即从 Salesforce 平台删除,而是先放入回收站(Recycle Bin)保留 15 天,期间可以恢复;15 天后才会被永久删除。

delete myRecord;      // 删除单条记录
delete recordList;    // 批量删除

DML 异常:如果 DML 操作失败,会返回 DmlException 类型的异常,可以在代码中捕获以处理错误。例如插入缺少必填 Name 字段的账户会触发 DmlException:

try {
    insert myRecord;
} catch (DmlException e) {
    for (Integer i = 0; i < e.getNumDml(); i++) {
        System.debug(e.getDmlMessage(i));
    }
}

Database 方法:部分成功

slide_17

Apex 内置的 Database 类提供与 DML 语句对应的静态方法:Database.insert()、Database.update()、Database.upsert()、Database.delete()、Database.undelete() 和 Database.merge()。

与 DML 语句不同,Database 方法有一个可选的 allOrNone 参数,用于指定操作是否可以部分成功。设为 false 时,如果部分记录出错,成功的记录会被提交,失败记录返回错误且不抛出异常。

Database 方法为每条记录返回包含成功或失败信息的结果对象(insert/update 返回 Database.SaveResult 列表,upsert 返回 Database.UpsertResult,delete 返回 Database.DeleteResult):

Database.SaveResult[] srList = Database.insert(conList, false);
for (Database.SaveResult sr : srList) {
    if (sr.isSuccess()) {
        System.debug('Successfully inserted contact. Contact ID: ' + sr.getId());
    } else {
        for (Database.Error err : sr.getErrors()) {
            System.debug(err.getStatusCode() + ': ' + err.getMessage());
        }
    }
}

用 DML 语句还是 Database 方法?若希望批量 DML 处理中任何错误都作为 Apex 异常抛出并立即中断控制流,使用 DML 语句(配合 try/catch);若希望允许部分成功(某条记录失败,其余仍能成功),使用 Database 类方法并检查结果列表。

处理关联记录

slide_18

如果两个对象之间已定义关系(如查找关系 lookup 或主从关系 master-detail),可以插入与已有记录相关联的记录。记录通过外键 ID 关联,例如插入新联系人时设置 AccountId 字段即可关联到账户。

Account acct = new Account(Name='SFDC Account');
insert acct;               // 先插入父记录
Contact mario = new Contact(FirstName='Mario', LastName='Ruiz',
    Phone='415.555.1212', AccountId=acct.ID);
insert mario;              // 再插入子记录

更新关联记录:关联记录上的字段不能用同一次 DML 调用更新,需要单独的 DML 调用(例如更新联系人时还要更新其关联账户,需要两次 update)。

删除关联记录:delete 支持级联删除——删除父对象会自动删除其子记录(只要子记录可删除)。例如删除账户会同时删除其关联联系人。

关于事务:DML 操作在事务内执行,一个事务内的所有 DML 要么全部成功,要么在某个操作出错时整个事务回滚、不提交任何数据。事务边界可以是触发器、类方法、匿名代码块、Apex 页面或自定义 Web 服务方法。

编写 SOQL 查询

slide_19

本单元学习 SOQL(Salesforce 对象查询语言)——用于读取 Salesforce 记录的查询语言,涵盖语法、过滤、排序、限制、变量以及关联关系查询。

开始使用 SOQL

slide_20

要读取 Salesforce 中的记录,必须编写查询。Salesforce 提供 Salesforce 对象查询语言(SOQL),它与标准 SQL 类似,但针对 Salesforce 平台定制。

由于 Apex 可以直接访问数据库中的 Salesforce 记录,可以把 SOQL 查询嵌入 Apex 代码,将 SOQL 语句用方括号括起来,并把返回值赋给 sObject 列表——这称为内联 SOQL(inline SOQL):

List<Account> accts = [SELECT Name, Phone FROM Account];

使用查询编辑器:开发者控制台提供 Query Editor,可运行 SOQL 查询并查看结果。先在 Query Editor 测试查询、再嵌入 Apex 代码是推荐做法——只需提供 SOQL 语句本身(不含外围 Apex 代码),点击 Execute 即可在结果网格中查看行和字段。

SOQL 基本语法:过滤、排序与限制

slide_21

基本 SOQL 语法为 SELECT field1, field2 FROM Object,WHERE 子句可选。SOQL 查询有两部分:SELECT 后列出要检索的字段(逗号分隔),FROM 后指定要检索的标准或自定义对象。

用 WHERE 过滤:在 WHERE 子句中添加条件,限制返回满足条件的记录(字符串比较不区分大小写)。比较运算符包括 =、!=、<、>、<=、>=、IN、NOT IN、LIKE、INCLUDES、EXCLUDES。也可以用 AND、OR 和括号组合多个条件。

SELECT Name, Phone FROM Account WHERE Industry = 'Technology'

用 ORDER BY 排序:默认返回顺序不确定,可用 ORDER BY 指定排序字段,ASC 为升序(默认),DESC 为降序。

用 LIMIT 限制:添加 LIMIT n 限制返回记录数(LIMIT 1 返回单个记录而非列表)。组合使用时顺序为 SELECT → FROM → WHERE → ORDER BY → LIMIT。

进阶:不能用 * 检索所有字段,改用 FIELDS() 关键字(FIELDS(STANDARD)、FIELDS(CUSTOM)、FIELDS(ALL));Id 字段在 Apex 查询中始终返回,无需显式指定。模糊匹配可用 LIKE 运算符(% 匹配任意字符,_ 匹配单个字符)。

SOQL 中的变量与关联记录查询

slide_22

在 SOQL 中访问变量:Apex 中的 SOQL 语句可以引用 Apex 代码变量和表达式,只需在前面加冒号(:),称为绑定(bind):

String targetIndustry = 'Technology';
List<Account> accts = [SELECT Name FROM Account WHERE Industry = :targetIndustry];

查询关联记录:记录可以通过查找关系或主从关系相互关联。要获取父记录的子记录,添加针对子记录的内查询(内查询的 FROM 子句使用关系名而非对象名);也可以从子对象(contact)通过点表示法遍历到父对象的字段(如 Account.Name):

SELECT Name, (SELECT LastName FROM Contacts) FROM Account
SELECT Name, Account.Name FROM Contact

自定义对象也可通过自定义关系关联,关系名以 __r 结尾(如 Line_Items__r)。

用 SOQL for 循环批量查询:SOQL for 循环把 SOQL 查询放在 for 循环中,采用高效分块(每次 200 条)检索记录,避免触及堆大小限制,推荐用 sObject 列表格式以便批量处理:

for (List<Account> accts : [SELECT Id FROM Account]) {
    // 每次处理 200 条记录的一批
}

编写 SOSL 查询

slide_23

本单元学习 SOSL(Salesforce 对象搜索语言)——用于跨多个对象进行文本搜索的查询语言。

开始使用 SOSL

slide_24

Salesforce 对象搜索语言(SOSL)是用于对记录执行文本搜索的 Salesforce 搜索语言,可同时搜索多个标准对象和自定义对象的字段。SOSL 类似于 Apache Lucene。

把 SOSL 查询直接嵌入 Apex 代码即可——嵌入 Apex 时称为内联 SOSL(inline SOSL)。

SOQL 与 SOSL 的区别:与 SOQL 一次只能查询一个对象不同,单条 SOSL 查询可以搜索所有对象。另一个区别是 SOSL 基于单词匹配,而 SOQL 默认精确匹配(不使用通配符时)。例如搜索 'Digital',SOSL 会返回字段值为 'Digital' 或 'The Digital Company' 的记录,而 SOQL 只返回字段值为 'Digital' 的记录。

何时使用:检索单个对象的记录用 SOQL;跨多个对象搜索字段用 SOSL(可搜索对象上的大部分文本字段)。同样可以用开发者控制台的 Query Editor 运行 SOSL 查询并查看结果(结果按对象分组到选项卡中)。注意:Query Editor 和 API 中的搜索词要放在花括号 {Wingo} 内,而 Apex 中用单引号 'Wingo'。

SOSL 基本语法

slide_25

SOSL 允许指定以下搜索条件:要搜索的文本表达式(单个单词或短语)、要搜索的字段范围、要检索的对象和字段列表,以及源对象中选择行的条件。

FIND {searchTerm} IN SearchGroup RETURNING ObjectType(fields)

SearchQuery 是要搜索的文本(单词或短语),可用逻辑运算符(AND、OR)和括号分组,也可包含通配符(* 匹配零个或多个字符,? 匹配单个字符)。文本搜索不区分大小写。

SearchGroup 是可选的搜索字段范围,默认搜索所有字段,取值可为 ALL FIELDS、NAME FIELDS、EMAIL FIELDS、PHONE FIELDS 或 SIDEBAR FIELDS。

ObjectsAndFields 是可选的要返回的信息——一个或多个 sObject 的列表,每个 sObject 内列出要返回的字段及可选的过滤条件。若未指定,搜索结果只包含所有找到对象的 ID。

搜索结果以列表的列表返回,每个元素对应一种对象类型,例如 results[0] 是 Account 匹配项、results[1] 是 Contact 匹配项。

搜索示例与 Apex 用法

slide_26

下面是几个 SOSL 搜索示例及其匹配结果(基于示例数据):

  • 搜索 The Query:返回所有字段同时包含 The 和 Query 的记录(顺序无关)——Account「The SFDC Query Man」的 Name 字段匹配。
  • 搜索 Wingo OR Man:使用 OR 逻辑,返回字段含 Wingo 或 Man 的记录——Contact「Carol Ruiz」(Department: Wingo)和 Account「The SFDC Query Man」匹配。
  • 搜索 1212:返回字段含单词 1212 的记录,电话号码以 -1212 结尾的字段因连字符分隔被视为单词而匹配。
  • 搜索 wing*:通配符搜索,返回字段值以 wing 开头的所有记录。

在 Apex 中使用 SOSL:先给变量 soslFindClause 赋搜索词(如 Wingo 和 SFDC 用 OR 组合),SOSL 查询用冒号绑定该变量,搜索结果以列表的列表返回:

List<List<SObject>> searchResults =
    [FIND :soslFindClause IN ALL FIELDS
     RETURNING Account(Name), Contact(FirstName, LastName)];
List<Account> accounts = (List<Account>) searchResults[0];
List<Contact> contacts = (List<Contact>) searchResults[1];

进阶(Tell Me More):可以在 RETURNING 子句内对每个对象分别过滤、排序和限制返回结果,例如 RETURNING Account(Name, Industry WHERE Industry='Apparel' ORDER BY Name LIMIT 10)。


文章来源:Trailhead - Apex Basics & Database