运行 Apex
你可以通过多种机制运行 Apex 代码:匿名块、触发器、异步 Apex、SOAP/REST Web 服务、电子邮件服务、Visualforce 控制器以及 JavaScript Remoting。所有 Apex 代码在原子事务中运行——要么完全成功,要么全部回滚。
匿名块
匿名块是不存储在元数据中但可以编译和执行的 Apex 代码。所需权限:API Enabled 和 Author Apex(通过 API 执行匿名 Apex 允许无 Author Apex 权限的受限访问)。使用 VS Code 扩展、Agentforce Vibes IDE、Developer Console 或 executeAnonymous() SOAP API 调用执行。
// 匿名块中可定义方法和异常——但不能使用 static 关键字
Integer int1 = 0;
void myProcedure1() { myProcedure2(); }
void myProcedure2() { int1++; }
myProcedure1();
关键规则:匿名块以当前用户身份运行,违反对象/字段权限时会编译失败。内容具有局部作用域——global 访问修饰符无实际意义。类/接口在匿名块中默认被视为 virtual。匿名块中定义的类/接口不会保存到组织。无需手动提交数据库更改——完全成功后自动提交,否则回滚。
重要:每次运行匿名块时代码及其引用都会被编译。对于重复调用,强烈建议使用编译类(如 Apex REST 端点)。Salesforce 阻止从托管包中调用的匿名 Apex。
触发器概述与语法
Apex 触发器在 Salesforce 记录发生更改(插入、更新、删除、合并、upsert、恢复)之前或之后执行自定义操作。所有触发器默认是批量触发器——应始终规划处理多条记录。
Before 触发器:用于在记录保存到数据库之前更新或验证记录值。After 触发器:用于访问系统设置的字段值(如 ID、LastModifiedDate),以及对其他记录产生影响(如写入审计表、触发异步事件)。After 触发器中的记录为只读。
trigger TriggerName on ObjectName (trigger_events) {
code_block
}
// 示例:Account 的 before insert 和 before update 事件
trigger myAccountTrigger on Account (before insert, before update) {
for (Account a : Trigger.new) {
if (a.Name == 'bad') { a.Name.addError('Bad name'); }
}
}
触发事件:before insert、before update、before delete、after insert、after update、after delete、after undelete。upsert 触发相应的 insert/update 事件。merge 触发被合并记录的 delete 事件和胜出记录的 update 事件。
触发器上下文变量
所有触发器定义隐式变量,包含在 System.Trigger 类中:
| 变量 | 说明 |
|---|---|
| isBefore / isAfter | 是否为 before/after 触发器 |
| isInsert / isUpdate / isDelete / isUndelete | 触发的操作类型 |
| isExecuting | 当前上下文是否为触发器 |
| new / newMap | 新版本记录列表/Map。仅在 insert/update/undelete 中可用,仅 before 中可修改 |
| old / oldMap | 旧版本记录列表/Map。仅在 update/delete 中可用。始终只读 |
| operationType | System.TriggerOperation 枚举(BEFORE_INSERT/AFTER_UPDATE 等) |
| size | 当前批次中的记录数(DML 超过 200 条时分为多批,每批单独触发) |
Trigger.new 的高级用法:不仅可以迭代 Trigger.new,还可以作为 SOQL 查询中的绑定变量——Apex 自动将记录列表转换为其 ID 列表:
trigger SimpleTrigger on Account(after insert) {
Contact[] cons = [SELECT LastName FROM Contact
WHERE AccountId IN :Trigger.new WITH USER_MODE];
}
上下文变量注意事项与批量惯用模式
关键约束:Trigger.new 和 Trigger.old 不能用于 Apex DML 操作。before insert 中只能更改字段值(不能 update/delete 原对象——尚未创建)。after insert 中可以 update/delete 原对象,但不能修改 Trigger.new(已保存)。
批量触发器惯用模式——使用 Set 和 Map:
trigger oppLineTrigger on OpportunityLineItem (before insert) {
// 使用 Set 收集唯一 PricebookEntry ID
Set<ID> pbeIds = new Set<ID>();
for (OpportunityLineItem oli : Trigger.new)
pbeIds.add(oli.pricebookentryid);
// 使用 Map 组织查询结果(按 ID 索引)
Map<ID, PricebookEntry> entries = new Map<ID, PricebookEntry>(
[SELECT product2.color__c FROM PricebookEntry WHERE Id IN :pbeIds]);
// 使用 Map 高效赋值
for (OpportunityLineItem oli : Trigger.new)
oli.color__c = entries.get(oli.pricebookEntryId).product2.color__c;
}
防止删除有引用记录的记录:
trigger oppTrigger on Opportunity (before delete) {
for (Quote__c q : [SELECT opportunity__c FROM Quote__c
WHERE opportunity__c IN :Trigger.oldMap.keySet()]) {
Trigger.oldMap.get(q.opportunity__c).addError('Cannot delete opportunity with a quote');
}
}
触发器执行顺序
当使用 insert/update/upsert 保存记录时,Salesforce 按以下顺序执行事件(服务器端):
- 从数据库加载原始记录或为 upsert 初始化记录
- 从请求加载新记录字段值并覆盖旧值(不同类型的请求执行不同的验证检查——标准 UI 执行布局规则/必填值/字段格式/最大长度;Apex/API 验证外键/字段格式/最大长度/受限选择列表)
- 执行配置为在记录保存前运行的记录触发 Flow
- 执行所有 before 触发器
- 再次运行大多数系统验证(必填字段非 null 检查、自定义验证规则——来自标准 UI 时不重复执行布局规则)
- 执行重复规则——如果判定为重复且使用阻止操作,记录不保存且不执行后续步骤
- 将记录保存到数据库(但未提交)
- 执行所有 after 触发器
- 执行分配规则
- 执行自动响应规则
- 执行工作流规则 + 如有字段更新再次触发 before/after update 触发器(仅一次)
- 执行升级规则
- 执行 Flow 自动化(Process Builder/Flow——无保证顺序)
- 执行保存后记录触发 Flow
- 执行权利规则
- 计算汇总摘要字段并更新父记录(递归到祖记录)
- 执行基于条件的共享评估
- 提交所有 DML 到数据库
- 执行提交后逻辑(发送邮件、异步 Apex 作业入队、Flow 异步路径)
注意:同一对象相同事件的多个触发器执行顺序不保证。递归保存期间跳过步骤 9-17。不触发触发器的操作包括:级联删除、合并中的子记录重新分配、批量状态更改、picklist 重命名等。
触发器特殊情况与注意事项
Before 触发器中不可更新的字段:某些字段在系统保存操作期间设置(在 before 触发器之后),包括 Task.isClosed、Opportunity.Amount(当有 line item 时)/ForecastCategory/isWon/isClosed、Contract.activatedDate、Case.isClosed、Id/createdDate(可检测但不可修改)等。
Merge 事件:不产生独立的触发器事件,而是触发被合并记录的 delete 事件和胜出记录的 update 事件。被删除记录的 MasterRecordId 字段仅在 after delete 触发器中可用。
触发器异常处理:使用 addError() 方法阻止 DML 操作——在 before 触发器中添加错误响应延迟更小。对 Trigger.new/Trigger.old 记录使用 addError 可在应用界面显示自定义错误消息。如果触发器抛出未处理异常,所有记录标记为错误。
触发器:Chatter 与 Knowledge 文章
Chatter 对象触发器:支持 FeedItem 和 FeedComment。只有特定类型(TextPost、QuestionPost、LinkPost 等)可插入并触发 before/after insert。FeedAttachment 不是可触发的对象——但可通过 SOQL 在 FeedItem update 触发器中访问。发布到私有组需要运行用户是该组成员或设置 CreatedById。
Knowledge 文章触发器:支持 KnowledgeArticleVersion 对象的各种操作——创建草稿、编辑、发布、翻译提交。但某些状态变更操作不触发触发器:从回收站恢复文章、预览和归档文章。迁移到 Lightning Knowledge 时必须移除引用 KAV 文章类型的触发器。
批量最佳实践总结:
- 永远不要假设触发器只处理一条记录——始终考虑批量场景
- 使用 Set 收集唯一 ID → 使用 Map 组织查询结果 → 在循环中通过 Map 高效检索
- 在集合中累积记录并对集合执行 DML,最小化 DML 操作次数
- 预处理记录生成 Set 放入单条 SOQL 语句的 IN 子句中,最小化 SOQL 查询数量
理解触发器的工作方式——从上下文变量到执行顺序再到批量模式——是编写健壮、高效 Apex 代码的核心技能。始终以批量思维设计触发器逻辑。