Apex 的 Trigger 类简介 Apex Triggers Trigger 结构与触发事件 Trigger 的预设变量 Trigger 中的错误处理 最佳实践:批量处理数据

Apex 的 Trigger 类简介
Apex Triggers
Trigger 结构与触发事件
Trigger 的预设变量
Trigger 中的错误处理
最佳实践:批量处理数据

Apex 触发器(Apex Triggers)是一种特殊的 Apex 类。它的主要作用是在一条记录被插入、修改、删除之前或之后自动执行一系列的操作。每一个 Trigger 类必须对应一种对象。

Trigger 的语法和普通的 Apex 类一样。

Salesforce 建议开发者在创建 Trigger 之前,考虑一下相同的操作可否通过 Salesforce 的设置界面中的功能完成,比如验证规则(Validation Rule)、工作流规则(Workflow Rule)等。如果可以,则优先使用它们。

Trigger 结构与触发事件

Trigger 的标准结构如下:

trigger Trigger名字 on 对象名字 (触发事件) {
    // Do something
}

Trigger 类必须以关键字 “trigger” 开始,然后是此 Trigger 的名字。接下来是 “on” 关键字,然后是 Trigger 对应的对象的名字。对象名字后面的括号中写入触发 Trigger 的事件。

Trigger 的触发事件分为以下几种:

  • before insert:插入数据之前
  • before update:更新数据之前
  • before delete:删除数据之前
  • after insert:插入数据之后
  • after update:更新数据之后
  • after delete:删除数据之后
  • after undelete:恢复数据之后

比如:

trigger HelloWorldTrigger on Account (before insert, before update) {
    System.debug('Hello World!');
}

这个 Trigger 在每一个 Account 对象插入或更新之前执行。

如果执行了如下代码:

Account a = new Account(Name='test');
insert a;

a.Name = 'test2';
update a;

在这两个 DML 语句生效前,系统的日志中会添加 “Hello World!” 的记录。

Trigger 的预设变量

Trigger 类可以使用一些系统预设的变量,来辅助实现一些公共的操作。

Trigger.New 和 Trigger.Old

Trigger.New 和 Trigger.Old 是两个预定义变量,可以用于每一个 Trigger 类中。前者代表了即将被插入、更新的数据,后者代表了更新之前、删除之前的数据。它们可能包含一条数据,也可能包含一组数据,取决于触发 Trigger 时的状态。

要注意的是,Trigger.New 不存在于 delete 操作中,因为删除之后就没有数据了。而 Trigger.Old 不存在于 insert 操作中,因为插入数据之前是没有数据的。

在下面的代码中,我们可以为每一条即将被插入数据库的 Account 数据定义 Description 字段的值。

trigger HelloWorldTrigger on Account (before insert) {
    for(Account acc : Trigger.New) {
        acc.Description = 'New account desc';
    }
}

通过这个 Trigger,当每一条 Account 数据被插入数据库之前,都会被定义 Description 字段的值。

布尔值变量

除了 Trigger.New 和 Trigger.Old,系统还提供了一系列的布尔值变量,用于标志 Trigger 或数据的状态。

最常用的有:

  • isInsert:是否是 insert 操作
  • isUpdate:是否是 update 操作
  • isDelete:是否是 delete 操作
  • isBefore:是否是操作之前
  • isAfter:是否是操作之后

在下面的代码中,我们可以定义对于不同的操作和不同的时机,写入不同的日志记录。

trigger HelloWorldTrigger on Account (before insert, after insert, before delete) {
    if(Trigger.isInsert) {
        if(Trigger.isBefore) {
            System.debug('This is before insert');
        } else if(Trigger.isAfter) {
            System.debug('This is after insert');
        }
    } else if(Trigger.isDelete) {
        System.debug('This is delete');
    }
}

Trigger 中的错误处理

在 Trigger 中,我们可以为进行操作的数据进行验证,类似于验证规则。如果遇到不符合条件的数据,可以通过 addError() 函数来将错误显示给用户,并记录日志。

在如下代码中,当一个“业务机会”对象被插入或更新之前,系统会检查“金额”字段的值是否不小于1000。如果“金额”的数值小于1000,该“业务机会”记录将不能被插入或更新。

错误信息的显示适用于前端和后端:

  • 如果该记录是从用户页面修改的,则用户会看到错误信息
  • 如果该记录是从 Apex 程序中被插入或修改,则错误信息会被记录在日志中
trigger OppyMaxAmountTrigger on Opportunity (before insert, before update) {
    for(Opportunity opp : Trigger.New) {
        if(opp.amount < 1000) {
            opp.addError('Amount should not be less than 1000!');
        }
    }
}

最佳实践:批量处理数据

由于 Trigger 类是在数据被操作的时候自动执行,而 Salesforce 是运行在云端的平台,所以对于 Trigger 类的一个最佳实践是:尽量批量处理数据,而非对每条数据在 Trigger 中单独处理。

比如在普通的 Apex 类中,有一段代码要更新一组 Contact 数据:

update contactList;

而系统中需要一个 Trigger 类在每个 Contact 对象更新之前设定其 FirstName 的值。

如果在 Trigger 中每次只处理一条数据,比如:

trigger ContactRenameTrigger on Contact (before update) {
    Contact c = Trigger.New[0];
    c.FirstName = 'default first name';
}

那么在一组 Contact 数据要更新的情况下,该 Trigger 会被执行很多次,每条数据一次。

如果将 Trigger 改为处理所有数据,比如:

trigger ContactRenameTrigger on Contact (before update) {
    for(Contact c : Trigger.New) {
        c.FirstName = 'default first name';
    }
}

那么 Trigger 只需要执行一次,就可以将要处理的一组 Contact 数据中每条数据都进行更新,提高了效率。

在 Trigger 中也可以执行 SOQL 查询和 DML 操作。这时也要尽量先准备好一组数据,然后执行一次 DML 操作,而非对每一条记录单独执行一次。

比如,在更新每个 Account 数据之后,都要插入一条 Opportunity 数据,那么可以在 Trigger 中先用 SOQL 查询提取出所有的 Account 数据,然后对于每一个 Account 数据新建一个 Opportunity 数据,再一次性将所有的 Opportunity 数据用 DML 语句插入数据库中。

trigger AddOpportunityTrigger on Account (after update) {
    List<Opportunity> oppList = new List<Opportunity>();

    List<Account> accList = [SELECT Id, Name FROM Account WHERE Id IN :Trigger.New];

    for(Account a : accList) {
        oppList.add(new Opportunity(Name=a.Name+' opp', 
                                    StageName='Prospecting',
                                    CloseDate=System.today().addMonths(1),
                                    AccountId=a.Id)
                                    );
    }

    if(oppList.size() > 0) {
        insert oppList;
    }
}

在上面的代码中,只执行了一次 SOQL 和 DML 语句。

如果在 for 循环中用 SOQL 单独查询出每一条 Account 的数据,然后新建一个 Opportunity 数据,并用 DML 插入这一条数据,那么执行效率会大大降低。