运行 Apex:匿名块、触发器与执行顺序完全指南

Apex运行机制完全指南:涵盖匿名块(权限要求/编译执行/local scope/virtual默认/禁止static)、触发器完整语法与事件类型(before vs after/upsert&merge行为)、全部上下文变量(Trigger.new/newMap/old/oldMap/isBefore/isAfter/isInsert/isUpdate/isDelete/isUndelete/operationType/size)与使用约束、批量触发器惯用模式(Set收集ID/Map组织查询结果/SOQL IN绑定)、完整20步服务器端执行顺序(Flow→before trigger→验证→重复规则→保存→after trigger→分配规则→工作流→汇总摘要→提交→提交后逻辑)、不触发触发器的操作列表以及Chatter/Knowledge文章触发器的特殊规则。...

📅 2026/7/16 ✍️ ponybai 🏷️ apex, salesforce

运行 Apex

运行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 中可用。始终只读
operationTypeSystem.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 按以下顺序执行事件(服务器端):

  1. 从数据库加载原始记录或为 upsert 初始化记录
  2. 从请求加载新记录字段值并覆盖旧值(不同类型的请求执行不同的验证检查——标准 UI 执行布局规则/必填值/字段格式/最大长度;Apex/API 验证外键/字段格式/最大长度/受限选择列表)
  3. 执行配置为在记录保存前运行的记录触发 Flow
  4. 执行所有 before 触发器
  5. 再次运行大多数系统验证(必填字段非 null 检查、自定义验证规则——来自标准 UI 时不重复执行布局规则)
  6. 执行重复规则——如果判定为重复且使用阻止操作,记录不保存且不执行后续步骤
  7. 将记录保存到数据库(但未提交)
  8. 执行所有 after 触发器
  9. 执行分配规则
  10. 执行自动响应规则
  11. 执行工作流规则 + 如有字段更新再次触发 before/after update 触发器(仅一次)
  12. 执行升级规则
  13. 执行 Flow 自动化(Process Builder/Flow——无保证顺序)
  14. 执行保存后记录触发 Flow
  15. 执行权利规则
  16. 计算汇总摘要字段并更新父记录(递归到祖记录)
  17. 执行基于条件的共享评估
  18. 提交所有 DML 到数据库
  19. 执行提交后逻辑(发送邮件、异步 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与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 代码的核心技能。始终以批量思维设计触发器逻辑。