Apex 的 Trigger 类简介

Apex Triggers

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 插入这一条数据,那么执行效率会大大降低。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,875评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,569评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,475评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,459评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,537评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,563评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,580评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,326评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,773评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,086评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,252评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,921评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,566评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,190评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,435评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,129评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,125评论 2 352

推荐阅读更多精彩内容