Skip to main content
欢迎参加 3 月 5 日至 6 日在旧金山举行的 TDX AI 代理时代开发者大会,或通过 Salesforce+ 参与。立即注册

Apex 触发器入门教程

学习目标

完成本单元后,您将能够:

  • 为 Salesforce 对象编写触发器。
  • 使用触发器上下文变量。
  • 从触发器中调用类方法。
  • 在触发器中使用 sObject addError() 方法限制保存操作。
备注

备注

用中文(简体)学习?在中文(简体)Trailhead Playground 中开始挑战,用括号中提供的译文完成挑战。仅复制并粘贴英文值,因为挑战验证基于英文数据。如果在中文(简体)组织中没有成功通过挑战,我们建议您 (1) 将区域设置切换为美国,(2) 按此处说明将语言切换为英文,(3) 再次单击“检查挑战”按钮。

查看 Trailhead 本地化语言徽章详细了解如何利用 Trailhead 译文。

开始之前

Apex 触发器有用、有趣且时髦。此模块不仅可帮助您开始使用它们,还引用了其他 Salesforce 功能来向您展示 Apex 触发器的强大功能。为了充分利用此模块,我们强烈建议您先查看以下模块:

编写 Apex 触发器

Apex 触发器使您能够在事件之前或之后对 Salesforce 中的记录执行自定义操作,例如添加、更新或删除。就像数据库系统支持触发器一样,Apex 为管理记录也提供了触发器支持。

通常,您根据特定条件使用触发器来执行某些操作、修改相关记录或限制某些操作的发生。您可以使用触发器来执行 Apex 中支持的任何操作,包括执行 SOQL 和 DML 或调用自定义 Apex 方法。

使用触发器来执行通过 Salesforce 用户界面中的单击式工具无法完成的任务。例如,如果要验证字段值或更新记录中的字段,请用验证规则和流。如果性能和规模很重要,或者逻辑对于点击式工具来说过于复杂,或者正在执行 CPU 密集型操作,请使用 Apex 触发器。

为顶级标准对象,例如客户或联系人、自定义对象和一些标准子对象定义触发器。触发器在创建时默认处于有效状态。当发生指定数据库事件时,Salesforce 自动触发有效触发器。

触发器语法

触发器定义的语法与类定义的语法不同。触发器定义以 trigger 关键字开头。然后是触发器的名称、触发器关联的 Salesforce 对象以及触发条件。触发器包含以下语法:

trigger TriggerName on ObjectName (trigger_events) {
   code_block
}

要在插入、更新、删除和取消删除操作之前或之后执行触发器,请在逗号分隔列表中指定多个触发器事件。可指定事件如下:

  • before insert
  • before update
  • before delete
  • after insert
  • after update
  • after delete
  • after undelete

触发器示例

在您插入客户并将消息写入调试日志之前,触发了这个简单的触发器。

  1. 在 Developer Console 中,单击 File(文件) | New(新建) | Apex Trigger(Apex 触发器)
  2. 输入 HelloWorldTrigger 作为触发器名称,然后为 sObject 选择 Account(客户)。单击 Submit(提交)
  3. 将默认代码替换为以下内容。


    trigger HelloWorldTrigger on Account (before insert) {
    	System.debug('Hello World!');
    }
  4. Ctrl+S 进行保存。
  5. 要测试触发器,请创建一个客户。
    1. 单击 Debug(调试) | Execute Anonymous Window(打开执行匿名窗口)
    2. 在新窗口中,添加以下内容,然后单击 Execute(执行)


      Account a = new Account(Name='Test Trigger');
      insert a;
  1. 在调试日志中,找到 Hello World! 语句。日志还会显示触发器已执行。

触发器类型

共有两种触发器类型。

  • Before 触发器通常用于在记录被保存到数据库之前更新或者校验记录值。
  • After 触发器用于访问系统设置的字段值(例如记录的 Id 或者 LastModifiedDate 字段),并影响其他记录中的更改。触发 after 触发器的记录为只读。

使用上下文变量

要访问导致触发器触发的记录,请使用上下文变量。例如,Trigger.new 包含 insert 或 update 触发器中插入的所有记录。Trigger.old 提供在 update 触发器中更新之前的旧版本 sObject,或 delete 触发器中已删除的 sObject 列表。当插入一条记录或通过 API 或 Apex 批量插入记录时,会触发触发器。因此,诸如 Trigger.new 之类的上下文变量只能包含一条或多条记录。您可以遍历 Trigger.new 来获取每个独立的 sObject。

此示例是 HelloWorldTrigger 示例触发器的修改版。它在 for 循环中遍历每个客户并更新每个客户的Description(描述)字段。

trigger HelloWorldTrigger on Account (before insert) {
    for(Account a : Trigger.new) {
        a.Description = 'New description';
    }
}
备注

触发器执行完毕后,系统保存 before 触发器的触发记录。您可以在不显式调用 DML 插入或更新操作的情况下修改触发器中的记录。如果对这些记录执行 DML 语句,则会出现错误。

其他部分上下文变量返回一个布尔值,以指示触发器是由于更新还是其他事件引起的触发。当触发器组合多个事件时,这些变量有很大的用处。例如:

trigger ContextExampleTrigger on Account (before insert, after insert, after delete) {
    if (Trigger.isInsert) {
        if (Trigger.isBefore) {
            // Process before insert
        } else if (Trigger.isAfter) {
            // Process after insert
        }
    }
    else if (Trigger.isDelete) {
        // Process after delete
    }
}

下表是可用于触发器的所有上下文变量的完整列表。

变量

使用情况

isExecuting

如果 Apex 代码的当前上下文是触发器,而不是 Visualforce 页面、Web 服务或 executeanonymous() API 调用,则返回 true。

isInsert

如果此触发器由于插入操作,并从 Salesforce 用户界面、Apex 或 API 触发,则返回 true

isUpdate

如果此触发器由于更新操作,并从 Salesforce 用户界面、Apex 或 API 触发,则返回 true

isDelete

如果此触发器由于删除操作,并从 Salesforce 用户界面、Apex 或 API 触发,则返回 true

isBefore

如果在保存任何记录之前触发此触发器,则返回 true

isAfter

如果在保存任何记录之后触发此触发器,则返回 true

isUndelete

如果在从回收站恢复记录后触发此触发器,则返回 true。从 Salesforce 用户界面、Apex 或 API 执行取消删除操作后,会出现恢复记录的情况。

new

返回 sObject 记录的新版本列表。

此 sObject 列表仅在 insertupdateundelete 触发器中可用,并且只能在 before 触发器中修改记录。

newMap

ID 到 sObject 新版本记录的映射。

此映射仅在 before updateafter insertafter update 以及 after undelete 触发器中可用。

old

返回 sObject 记录的旧版本列表。

此 sObject 列表仅在 updatedelete 触发器中可用。

oldMap

ID 到 sObject 旧版本记录的映射。

此映射仅在 updatedelete 触发器中可用。

operationType

返回与当前操作对应的 System.TriggerOperation 类型的枚举。

System.TriggerOperation 枚举的可能值包括:BEFORE_INSERTBEFORE_UPDATEBEFORE_DELETEAFTER_INSERTAFTER_UPDATEAFTER_DELETEAFTER_UNDELETE。如果您根据不同的触发器类型改变编程逻辑,请考虑使用 switch 语句,该语句具有独特触发器执行枚举状态的不同排列。

size

触发器调用中包含的新旧记录总数。

从触发器中调用类方法

您可以从触发器中调用公共实用程序方法。调用其他类的方法可实现代码重用、减少触发器的大小并改进 Apex 代码的维护。还允许您使用面向对象的编程。

以下示例展示了如何从触发器调用静态方法。如果触发器因插入事件而被触发,该示例将调用 EmailManager 类上的静态 sendMail() 方法。该实用程序方法向指定收件人发送电子邮件,并包含插入的联系人记录数。

  1. 在 Developer Console 中,单击 File(文件)| New(新建)| Apex Class(Apex 类)
  2. 输入 EmailManager,然后单击 OK(确定)。
  3. 用下面的 EmailManager 类示例替换默认类主体。
    public class EmailManager {
        // Public method
        public static void sendMail(String address, String subject, String body) {
            // Create an email message object
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
            String[] toAddresses = new String[] {address};
            mail.setToAddresses(toAddresses);
            mail.setSubject(subject);
            mail.setPlainTextBody(body);
            // Pass this email message to the built-in sendEmail method
            // of the Messaging class
            Messaging.SendEmailResult[] results = Messaging.sendEmail(
                                      new Messaging.SingleEmailMessage[] { mail });
            // Call a helper method to inspect the returned results
            inspectResults(results);
        }
        // Helper method
        private static Boolean inspectResults(Messaging.SendEmailResult[] results) {
            Boolean sendResult = true;
            // sendEmail returns an array of result objects.
            // Iterate through the list to inspect results.
            // In this class, the methods send only one email,
            // so we should have only one result.
            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;
        }
    }
  4. 在 Developer Console 中,单击 File(文件) | New(新建) | Apex Trigger(Apex 触发器)
  5. 输入 ExampleTrigger 作为触发器名称,然后为 sObject 选择 Contact(联系人)。单击 Submit(提交)
  6. 将默认代码替换为以下内容,然后将 sendMail() 中的电子邮件地址占位符文本修改为您的电子邮件地址。
    trigger ExampleTrigger on Contact (after insert, after delete) {
        if (Trigger.isInsert) {
            Integer recordCount = Trigger.new.size();
            // Call a utility method from another class
            EmailManager.sendMail('Your email address', 'Trailhead Trigger Tutorial',
                        recordCount + ' contact(s) were inserted.');
        }
        else if (Trigger.isDelete) {
            // Process after delete
        }
    }
  7. Ctrl+S 进行保存。
  8. 要测试触发器,请创建一个联系人。
    1. 单击 Debug(调试) | Execute Anonymous Window(打开执行匿名窗口)
    2. 在新窗口中,添加以下内容,然后单击 Execute(执行)
      Contact c = new Contact(LastName='Test Contact');
      insert c;
  1. 在调试日志中,检查触发器是否被触发。在日志末尾,找到由实用程序方法写入的调试消息:DEBUG|Email sent successfully
  2. 现在检查您是否收到一封电子邮件,正文文本内容为 1 contact(s) were inserted(已插入 1 个联系人)。

    有了新的触发器,每次添加一个或多个联系人时您都会收到一封电子邮件!

触发器通常用于访问和管理与触发器上下文中的记录相关的记录 — 即导致此触发器触发的记录。

如果客户没有关联的业务机会,则该触发器将为每个新增或更新的客户添加一个业务机会。首先,触发器会执行 SOQL 查询以获取触发器触发的客户的所有子业务机会。然后,触发器遍历 Trigger.new 中的 sObject 列表以获取每个客户的 sObject。如果客户没有关联的业务机会 sObject,则 for 循环会为其创建一个 sObject。如果触发器创建了任意业务机会,则最终执行的语句会将其插入进来。

  1. 使用 Developer Console 添加以下触发器(遵循 HelloWorldTrigger 示例中的步骤,但要使用 AddRelatedRecord 作为触发器名称)。
    trigger AddRelatedRecord on Account(after insert, after update) {
        List<Opportunity> oppList = new List<Opportunity>();
        // Get the related opportunities for the accounts in this trigger
        Map<Id,Account> acctsWithOpps = new Map<Id,Account>(
            [SELECT Id,(SELECT Id FROM Opportunities) FROM Account WHERE Id IN :Trigger.new]);
        // Add an opportunity for each account if it doesn't already have one.
        // Iterate through each account.
        for(Account a : Trigger.new) {
            System.debug('acctsWithOpps.get(a.Id).Opportunities.size()=' + acctsWithOpps.get(a.Id).Opportunities.size());
            // Check if the account already has a related opportunity.
            if (acctsWithOpps.get(a.Id).Opportunities.size() == 0) {
                // If it doesn't, add a default opportunity
                oppList.add(new Opportunity(Name=a.Name + ' Opportunity',
                                           StageName='Prospecting',
                                           CloseDate=System.today().addMonths(1),
                                           AccountId=a.Id));
            }
        }
        if (oppList.size() > 0) {
            insert oppList;
        }
    }
  2. 要测试该触发器,请在 Salesforce 用户界面中创建一个客户并将其命名为 Apples & Oranges
  3. 在客户页面的业务机会相关列表中,找到新创建的业务机会。触发器自动添加了该业务机会!
备注

您添加的触发器遍历触发器上下文的所有记录 — for 循环遍历 Trigger.new。但是,该触发器中的循环可能更有效。在此触发器上下文中,我们并不需要访问每一个客户,只需要访问没有业务机会的客户的一个子集。下一个单元将演示如何让触发器更高效。在“批量触发器设计模式”单元中,学习如何通过修改 SOQL 查询,实现仅获取没有业务机会的客户。然后,对这些记录进行遍历。

使用触发器异常

您有时需要对某些数据库操作添加限制,例如在满足某些条件时防止保存记录。要防止在触发器中保存记录,请对相关 sObject 调用 addError() 方法。addError() 方法在触发器内部抛出一个致命错误。用户界面将显示并记录该错误提示。

以下触发器可防止删除具有相关业务机会的客户。默认情况下,删除客户会引起其所有关联记录的级联删除。该触发器可防止业务机会的级联删除操作。您试试这款触发器吧!如果您执行了上一个示例,组织会有一个名为 Apples & Oranges 的客户,还包含了关联的业务机会。本示例中使用了示例客户。

  1. 通过 Developer Console 添加以下触发器。
    trigger AccountDeletion on Account (before delete) {
        // Prevent the deletion of accounts if they have related opportunities.
        for (Account a : [SELECT Id FROM Account
                         WHERE Id IN (SELECT AccountId FROM Opportunity) AND
                         Id IN :Trigger.old]) {
            Trigger.oldMap.get(a.Id).addError(
                'Cannot delete account with related opportunities.');
        }
    }
  2. 在 Salesforce 用户界面,导航至 Apples & Oranges 客户页面,然后单击 Delete(删除)
  3. 在确认弹出窗口中,单击 OK(确定)。找到显示自定义错误提示消息

    找到显示自定义错误提示消息 Cannot delete account with related opportunities(无法删除拥有关联业务机会的客户)的验证错误。

  4. 禁用 AccountDeletion 触发器。如果该触发器处于有效状态,则无法检查错误。
    1. 在 Setup(设置)中,搜索 Apex Triggers(Apex 触发器)。
    2. 在 Apex Triggers(Apex 触发器)页面中,单击 AccountDeletion 触发器旁边的 Edit(编辑)
    3. 取消选择 Is Active(有效)
    4. 单击保存
备注

除非批量 DML 调用时部分成功,否则在触发器中调用 addError() 会导致整个操作集回滚。

  • 如果 Lightning 平台 API 中的批量 DML 调用生成了触发器,则运行时引擎会将不良记录放在一边。然后,运行时引擎尝试保存部分未生成错误的记录。
  • 如果 Apex 中的 DML 语句生成了触发器,则任何错误都会回滚整个操作。但是,运行时引擎仍会处理操作中的每条记录以编译完整的错误列表。

触发器和调出

Apex 允许您调用 Apex 代码并将其与外部 Web 服务集成。对外部 Web 服务的 Apex 调用称之为调出。例如,您可以调出股票报价服务以获取最新报价。当从触发器发起调出时,必须异步执行,以便触发器进程不会在等待外部服务响应时阻止您的工作。异步调出在后台进程中进行,当外部服务返回结果时接收到响应。

要从触发器发起调出,请使用异步执行的类方法。这个类方法被称为 future 方法,并用 @future(callout=true) 注释。示例类中包含发起调出的 future 方法。

备注

本示例出于演示的目的,使用了虚构的端点 URL。除非您将端点更改为有效的 URL,并在 Salesforce 中为您的端点添加远程站点,否则您无法运行此示例。

public class CalloutClass {
    @future(callout=true)
    public static void makeCallout() {
        HttpRequest request = new HttpRequest();
        // Set the endpoint URL.
        String endpoint = 'http://yourHost/yourService';
        request.setEndPoint(endpoint);
        // Set the HTTP verb to GET.
        request.setMethod('GET');
        // Send the HTTP request and get the response.
        HttpResponse response = new HTTP().send(request);
    }
}

本示例展示了调用类中的方法以异步调出触发器。

trigger CalloutTrigger on Account (before insert, before update) {
    CalloutClass.makeCallout();
}

本章节仅提供关于调出的简要介绍,不作深入探讨。有关更多信息,请参见 Apex 开发人员指南中的使用 Apex 发起调出

资源

实践挑战

+500 分

准备好

您将在您自己的实践组织中完成此单元。单击启动以开始,或单击您的组织的名称以选择不同的组织。

您的挑战

Create an Apex trigger

Create an Apex trigger that sets an account’s Shipping Postal Code to match the Billing Postal Code if the Match Billing Address option is selected. Fire the trigger before inserting an account or updating an account.

Pre-Work:
Add a checkbox field to the Account object:

  • Field Label: Match Billing Address
  • Field Name: Match_Billing_Address
    Note: The resulting API Name should be Match_Billing_Address__c.

  • Create an Apex trigger:
    • Name: AccountAddressTrigger
    • Object: Account
    • Events: before insert and before update
    • Condition: Match Billing Address is true
    • Operation: set the Shipping Postal Code to match the Billing Postal Code
在 Salesforce 帮助中分享 Trailhead 反馈

我们很想听听您使用 Trailhead 的经验——您现在可以随时从 Salesforce 帮助网站访问新的反馈表单。

了解更多 继续分享反馈