您好,登录后才能下订单哦!
# Salesforce的Trigger触发器怎么使用
## 目录
1. [Trigger基础概念](#1-trigger基础概念)
- 1.1 [什么是Trigger](#11-什么是trigger)
- 1.2 [Trigger执行上下文](#12-trigger执行上下文)
- 1.3 [Trigger与工作流规则的区别](#13-trigger与工作流规则的区别)
2. [Trigger语法结构](#2-trigger语法结构)
- 2.1 [基本语法格式](#21-基本语法格式)
- 2.2 [Trigger事件类型](#22-trigger事件类型)
- 2.3 [上下文变量](#23-上下文变量)
3. [Trigger最佳实践](#3-trigger最佳实践)
- 3.1 [批量处理原则](#31-批量处理原则)
- 3.2 [避免递归触发](#32-避免递归触发)
- 3.3 [使用Handler模式](#33-使用handler模式)
4. [常见Trigger模式](#4-常见trigger模式)
- 4.1 [字段自动填充](#41-字段自动填充)
- 4.2 [数据验证](#42-数据验证)
- 4.3 [关联记录操作](#43-关联记录操作)
5. [高级Trigger技巧](#5-高级trigger技巧)
- 5.1 [静态变量控制递归](#51-静态变量控制递归)
- 5.2 [异步Trigger处理](#52-异步trigger处理)
- 5.3 [Trigger与Future方法结合](#53-trigger与future方法结合)
6. [调试与测试](#6-调试与测试)
- 6.1 [调试日志分析](#61-调试日志分析)
- 6.2 [测试类编写规范](#62-测试类编写规范)
- 6.3 [测试覆盖率提升](#63-测试覆盖率提升)
7. [性能优化](#7-性能优化)
- 7.1 [SOQL查询优化](#71-soql查询优化)
- 7.2 [DML操作优化](#72-dml操作优化)
- 7.3 [集合使用技巧](#73-集合使用技巧)
8. [常见问题与解决方案](#8-常见问题与解决方案)
- 8.1 [Governor Limit处理](#81-governor-limit处理)
- 8.2 [混合DML错误](#82-混合dml错误)
- 8.3 [死锁问题](#83-死锁问题)
9. [实际案例解析](#9-实际案例解析)
- 9.1 [客户评分自动计算](#91-客户评分自动计算)
- 9.2 [订单状态同步](#92-订单状态同步)
- 9.3 [跨对象数据集成](#93-跨对象数据集成)
10. [总结与资源](#10-总结与资源)
## 1. Trigger基础概念
### 1.1 什么是Trigger
Salesforce Trigger(触发器)是Apex代码的一种特殊类型,它在特定数据操作事件(如记录插入、更新、删除等)发生时自动执行。Trigger允许开发人员在数据变更前后执行自定义业务逻辑,是实现复杂业务流程的核心工具。
**关键特性:**
- 与特定sObject关联
- 响应DML操作自动触发
- 在事务上下文中执行
- 可以访问操作记录的旧值和新值
### 1.2 Trigger执行上下文
Trigger在特定的执行上下文中运行,理解这些上下文对编写高效Trigger至关重要:
| 上下文变量 | 描述 |
|------------------|----------------------------------------------------------------------|
| `Trigger.isBefore` | 在数据保存到数据库前执行 |
| `Trigger.isAfter` | 在数据保存到数据库后执行 |
| `Trigger.new` | 包含要插入或更新的记录新版本(仅适用于insert和update操作) |
| `Trigger.old` | 包含更新前或删除前的记录旧版本(仅适用于update和delete操作) |
| `Trigger.size` | 触发器中包含的记录总数 |
### 1.3 Trigger与工作流规则的区别
虽然Trigger和工作流规则都能实现自动化业务逻辑,但二者有本质区别:
| 特性 | Trigger | 工作流规则 |
|---------------------|----------------------------------|----------------------------|
| 灵活性 | 完全编程控制 | 配置化,功能有限 |
| 处理能力 | 支持复杂业务逻辑 | 简单字段更新和通知 |
| 执行上下文 | Before/After均可 | 仅After |
| 批量处理 | 原生支持 | 单条记录处理 |
| 测试要求 | 需要75%以上测试覆盖率 | 无需测试 |
| 调试复杂度 | 较高 | 较低 |
## 2. Trigger语法结构
### 2.1 基本语法格式
```apex
trigger TriggerName on ObjectName (trigger_events) {
// Trigger逻辑代码
}
示例:
trigger AccountTrigger on Account (before insert, after update) {
if(Trigger.isBefore && Trigger.isInsert) {
// 插入前逻辑
} else if(Trigger.isAfter && Trigger.isUpdate) {
// 更新后逻辑
}
}
Salesforce支持7种Trigger事件:
before insert
- 记录插入前after insert
- 记录插入后before update
- 记录更新前after update
- 记录更新后before delete
- 记录删除前after delete
- 记录删除后after undelete
- 记录恢复后完整上下文变量列表:
// 判断Trigger类型
Trigger.isExecuting
Trigger.isInsert
Trigger.isUpdate
Trigger.isDelete
Trigger.isUndelete
Trigger.isBefore
Trigger.isAfter
// 记录集合
Trigger.new
Trigger.newMap
Trigger.old
Trigger.oldMap
// 其他信息
Trigger.operationType
Trigger.size
关键点: - 所有Trigger都应设计为能处理200条记录的批量操作 - 避免在循环内执行SOQL查询或DML操作 - 使用集合类(List, Set, Map)处理记录
不良实践:
trigger BadExample on Contact (before update) {
for(Contact c : Trigger.new) {
// 循环内执行SOQL - 违反批量原则
Account a = [SELECT Id FROM Account WHERE Name = :c.Account_Name__c];
c.AccountId = a.Id;
}
}
优化后:
trigger GoodExample on Contact (before update) {
Set<String> accountNames = new Set<String>();
for(Contact c : Trigger.new) {
accountNames.add(c.Account_Name__c);
}
Map<String, Account> accountMap = new Map<String, Account>();
for(Account a : [SELECT Id, Name FROM Account WHERE Name IN :accountNames]) {
accountMap.put(a.Name, a);
}
for(Contact c : Trigger.new) {
if(accountMap.containsKey(c.Account_Name__c)) {
c.AccountId = accountMap.get(c.Account_Name__c).Id;
}
}
}
递归触发会导致Governor Limit问题和不可预期的行为:
解决方案: 1. 使用静态变量控制
public class TriggerControl {
public static Boolean isFirstRun = true;
}
trigger AccountTrigger on Account (before update) {
if(TriggerControl.isFirstRun) {
TriggerControl.isFirstRun = false;
// 业务逻辑
}
}
将业务逻辑从Trigger转移到单独的Handler类:
// Trigger文件
trigger AccountTrigger on Account (before insert, before update) {
AccountTriggerHandler.handleBeforeInsertUpdate(Trigger.new);
}
// Handler类
public class AccountTriggerHandler {
public static void handleBeforeInsertUpdate(List<Account> accounts) {
// 业务逻辑实现
}
}
优势: - 代码更易维护 - 逻辑可复用 - 便于单元测试 - 减少Trigger复杂度
trigger OpportunityTrigger on Opportunity (before insert, before update) {
for(Opportunity opp : Trigger.new) {
// 自动设置关闭日期为创建日期+30天
if(opp.CloseDate == null) {
opp.CloseDate = Date.today().addDays(30);
}
// 自动计算金额
if(opp.Quantity__c != null && opp.UnitPrice__c != null) {
opp.Amount = opp.Quantity__c * opp.UnitPrice__c;
}
}
}
trigger ContactTrigger on Contact (before insert, before update) {
for(Contact con : Trigger.new) {
// 验证电子邮件格式
if(con.Email != null && !Pattern.matches('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}', con.Email)) {
con.Email.addError('请输入有效的电子邮件地址');
}
// 业务规则验证
if(con.Department == 'Finance' && con.Title == null) {
con.Title.addError('财务部门联系人必须指定职位');
}
}
}
trigger CaseTrigger on Case (after insert) {
if(Trigger.isAfter && Trigger.isInsert) {
List<Task> tasks = new List<Task>();
for(Case c : Trigger.new) {
// 为每个新Case创建跟进任务
tasks.add(new Task(
WhatId = c.Id,
Subject = '初始跟进',
Priority = 'Normal',
Status = 'Not Started',
ActivityDate = Date.today().addDays(1)
);
}
if(!tasks.isEmpty()) {
insert tasks;
}
}
}
public class RecursionControl {
private static Set<Id> processedAccountIds = new Set<Id>();
public static Boolean hasAlreadyProcessed(Id accountId) {
if(processedAccountIds.contains(accountId)) {
return true;
} else {
processedAccountIds.add(accountId);
return false;
}
}
}
trigger AccountTrigger on Account (after update) {
for(Account acc : Trigger.new) {
if(!RecursionControl.hasAlreadyProcessed(acc.Id)) {
// 执行业务逻辑
}
}
}
trigger OrderTrigger on Order (after update) {
if(Trigger.isAfter && Trigger.isUpdate) {
Set<Id> orderIds = new Set<Id>();
for(Order o : Trigger.new) {
if(o.Status == 'Activated' && Trigger.oldMap.get(o.Id).Status != 'Activated') {
orderIds.add(o.Id);
}
}
if(!orderIds.isEmpty()) {
System.enqueueJob(new OrderProcessingQueueable(orderIds));
}
}
}
trigger ContactTrigger on Contact (after update) {
if(Trigger.isAfter && Trigger.isUpdate) {
Set<Id> accountIds = new Set<Id>();
for(Contact con : Trigger.new) {
if(con.AccountId != null && con.Email != Trigger.oldMap.get(con.Id).Email) {
accountIds.add(con.AccountId);
}
}
if(!accountIds.isEmpty()) {
updateAccountContactsFuture(accountIds);
}
}
}
@future
public static void updateAccountContactsFuture(Set<Id> accountIds) {
// 异步处理逻辑
}
使用System.debug()输出调试信息:
trigger DebugExample on Account (before update) {
System.debug('Trigger.new size: ' + Trigger.new.size());
for(Account acc : Trigger.new) {
System.debug('Processing account: ' + acc.Id);
System.debug('Old Name: ' + Trigger.oldMap.get(acc.Id).Name);
System.debug('New Name: ' + acc.Name);
}
}
查看日志: 1. 设置 → 监控 → 调试日志 2. 添加用户跟踪 3. 执行触发操作 4. 查看生成的调试日志
@isTest
private class AccountTriggerTest {
@isTest
static void testBeforeInsert() {
// 准备测试数据
Account testAcc = new Account(
Name = 'Test Account',
Industry = 'Technology'
);
Test.startTest();
insert testAcc;
Test.stopTest();
// 验证结果
Account insertedAcc = [SELECT CustomerPriority__c FROM Account WHERE Id = :testAcc.Id];
System.assertEquals('High', insertedAcc.CustomerPriority__c, '优先级未正确设置');
}
@isTest
static void testBulkInsert() {
// 批量测试
List<Account> accounts = new List<Account>();
for(Integer i=0; i<200; i++) {
accounts.add(new Account(
Name = 'Test Account ' + i
));
}
Test.startTest();
insert accounts;
Test.stopTest();
// 验证所有记录处理
List<Account> insertedAccounts = [SELECT Id FROM Account WHERE Name LIKE 'Test Account %'];
System.assertEquals(200, insertedAccounts.size());
}
}
技巧: 1. 测试所有Trigger上下文 2. 测试正向和负向场景 3. 测试批量操作 4. 测试异常情况 5. 使用Test.loadData方法加载测试数据 6. 利用@testSetup方法
最佳实践: - 将SOQL移出循环 - 使用WHERE IN子句 - 只查询需要的字段 - 利用索引字段(标准索引字段:Id, Name, 外部ID, 主从关系字段等)
// 差: 循环内查询
for(Contact c : Trigger.new) {
Account a = [SELECT Id, Name FROM Account WHERE Id = :c.AccountId];
}
// 好: 批量查询
Set<Id> accountIds = new Set<Id>();
for(Contact c : Trigger.new) {
accountIds.add(c.AccountId);
}
Map<Id, Account> accountMap = new Map<Id, Account>([SELECT Id, Name FROM Account WHERE Id IN :accountIds]);
关键点: - 批量执行DML操作 - 使用Database方法处理部分成功 - 考虑使用急切加载
// 差: 循环内插入
for(Contact c : contactsToCreate) {
insert c;
}
// 好: 批量插入
insert contactsToCreate;
// 更好: 带错误处理的部分成功
Database.SaveResult[] srList = Database.insert(contactsToCreate, false);
for(Database.SaveResult sr : srList) {
if(!sr.isSuccess()) {
System.debug('插入失败: ' + sr.getErrors());
}
}
高效集合操作:
// 使用Set去重
Set<Id> accountIds = new Set<Id>();
for(Opportunity opp : Trigger.new) {
accountIds.add(opp.AccountId);
}
// 使用Map快速查找
Map<Id, Account> accountMap = new Map<Id, Account>([SELECT Id, Name FROM Account WHERE Id IN :accountIds]);
// 使用List保持顺序
List<Account> sortedAccounts = new List<Account>();
sortedAccounts.addAll(accountMap.values());
sortedAccounts.sort();
常见限制及解决方案:
限制类型 | 限制值 | 解决方案 |
---|---|---|
SOQL查询总数 | 100次 | 批量查询,使用Map缓存结果 |
DML语句总数 | 150次 | 批量DML操作 |
CPU时间 | 10,000毫秒 | 优化算法复杂度,考虑异步处理 |
堆大小 | 6MB/12MB | 减少变量使用,分批次处理数据 |
查询返回记录数 | 50,000条 | 添加更精确的WHERE条件,使用LIMIT子句 |
问题描述: 在同一个事务中处理设置对象(setup object)和非设置对象时会出现混合DML错误。
解决方案:
// 错误示例
trigger UserTrigger on User (after update) {
// 同时更新用户和普通对象会报错
update [SELECT Id FROM Account LIMIT 1];
}
// 正确方式:使用@future方法
trigger UserTrigger on User (after update) {
System.enqueueJob(new MixedDMLHandler(Trigger.newMap.keySet()));
}
public class MixedDMLHandler implements Queueable {
private Set<Id> userIds;
public MixedDMLHandler(Set<Id> userIds) {
this.userIds = userIds;
}
public void execute(QueueableContext context) {
// 这里可以安全执行混合DML
update [SELECT Id FROM Account LIMIT 1];
}
}
预防措施: 1. 避免在Trigger中更新触发对象自身 2. 减少交叉对象更新 3. 使用异步处理分离事务 4. 优化SOQL查询条件减少锁定范围
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。