Apex 中的数据操作:sObject 与 DML 完全指南

Apex 数据操作完全指南:深入详解 sObject 类型系统(通用/特定类型转换与字段设置状态)、DML 操作(单条vs批量/批量推荐模式)、DML 语句 vs Database 类方法(部分成功处理)、原子事务、全部 DML 操作(Insert/Update/Upsert/Merge/Delete/Undelete/ConvertLead)含关联记录与外部 ID、异常处理与结果对象、DMLOptions(字段截断/分配规则/重复规则/邮件/语言/部分成功)、保存点与事务回滚(releaseSavepoint与callout)、混合DML(setup对象/@future解决)、记录锁定(FOR UPDATE/10秒超时/死锁避免)、以及批量DML异常重试机制和数据约束。...

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

在 Apex 中处理数据

数据操作

你可以在 Lightning Platform 持久层中添加和操作数据。sObject 数据类型是保存数据对象的主要数据类型。你将使用数据操作语言(DML)来处理数据,并使用查询语言(SOQL/SOSL)来检索数据。

使用 sObject

sObject

sObject 变量代表一行数据,只能使用对象的 SOAP API 名称在 Apex 中声明。相似于 SOAP API,Apex 允许使用通用 sObject 抽象类型来表示任何对象。

Account a = new Account();
MyCustomObject__c co = new MyCustomObject__c();

// 通用 sObject 类型
sObject s = new Account();
// 类型转换
Account a2 = (Account) s;  // 安全——s 实际上是 Account
// Contact c = (Contact) s;  // 运行时错误

sObject 变量初始化为 null,可以使用 new 运算符赋值。实例化时可以通过逗号分隔的 name=value 对指定初始字段值:

Account a = new Account(name = 'Acme', billingcity = 'San Francisco');

访问 sObject 字段与验证

sObject 字段

与 Java 一样,sObject 字段可以通过点符号访问:a.Name = 'Acme';。系统生成的字段(Created By、Last Modified Date)不能修改。公式字段值和当前用户只读的字段值也不能更改。

通用 sObject vs 特定 sObject:使用通用 sObject 类型时,只能通过点符号访问 Id 字段。对于其他字段,需要使用 putget 方法。建议先将通用 sObject 转换为特定对象后再操作:

Account convertedAccount = (Account) s;
convertedAccount.name = 'Acme2';
update convertedAccount;

字段设置状态:sObject 字段可以是"已设置"或"未设置"(unset)。未设置的字段不等于 null 或空。使用 System.isSet() 方法识别。擦除字段当前值应设置为 null。Boolean 类型 sObject 字段的表达式仅在该字段为 true 时求值为 true。

数据操作语言 (DML)

DML

Apex 允许你使用 DML 在数据库中插入、更新、删除或恢复数据。与 SOQL(用于读操作)不同,DML 用于写操作。

单条 vs 批量 DML 操作

强烈推荐批量 DML 操作——对 sObject 列表执行 DML 操作计为一条 DML 语句而不是每条记录一条。这有助于避免达到 Governor Limit(每个 Apex 事务 150 条 DML 语句,总计 10,000 行)。

// 不好:每条记录单独更新
for (Contact c : conList) {
    if (c.Department == 'Finance') {
        c.Description = 'New description';
        update c;  // 每条记录计为一条 DML
    }
}

// 推荐:批量更新
List<Contact> updatedList = new List<Contact>();
for (Contact c : conList) {
    if (c.Department == 'Finance') {
        c.Description = 'New description';
        updatedList.add(c);
    }
}
update updatedList;  // 整个列表只计一条 DML
批量DML

DML 语句 vs Database 类方法

DML vs Database

Apex 提供两种方式执行 DML 操作:

DML 语句Database 类方法
更直接、更简单更灵活
错误时抛出异常可指定是否部分成功(第二个 Boolean 参数,默认 true=全部或全无)
立即中断控制流返回结果对象数组(含成功/失败状态和错误信息)
// DML 语句
insert acctList;

// Database 类方法——允许部分成功
Database.SaveResult[] srList = Database.insert(acctList, false);
for (Database.SaveResult sr : srList) {
    if (sr.isSuccess()) {
        System.debug('Successfully inserted. ID: ' + sr.getId());
    } else {
        for (Database.Error err : sr.getErrors()) {
            System.debug(err.getStatusCode() + ': ' + err.getMessage());
        }
    }
}

选择指南:使用 DML 语句如果你希望错误立即作为 Apex 异常抛出(使用 try-catch 处理)。使用 Database 类方法如果你希望批量操作允许部分成功——失败的记录不影响其他记录。

注意:convertLead 仅作为 Database 类方法可用,不是 DML 语句。Database 类还提供事务控制、回滚和清空回收站等方法。

原子事务与 DML 操作

原子事务

DML 操作在事务内执行。事务中的所有 DML 操作要么完全成功,要么在一个操作发生错误时整个事务回滚——没有数据被提交到数据库。事务的边界可以是触发器、类方法、匿名代码块、Apex 页面或自定义 Web 服务方法。从事务边界调用外部代码的所有操作代表一个单一的操作单元。

插入、更新与关联记录

插入更新

插入记录:创建 sObject 并调用 insert。可以插入关联记录(如果两个对象之间已存在关系,如查找或主-详细信息关系):

Account acct = new Account(Name='SFDC Account');
insert acct;
Contact con = new Contact(FirstName='Joe', LastName='Smith', Phone='415.555.1212', AccountId=acct.ID);
insert con;

更新关联记录:关联记录上的字段不能在同一次 DML 调用中更新——需要分别调用

// 更新联系人的电话 AND 关联账户的行业需要两次 update
queriedContact.Phone = '415.555.1213';
queriedContact.Account.Industry = 'Technology';
update queriedContact;         // 更新电话
update queriedContact.Account;  // 更新行业

通过外部 ID 关联记录:使用自定义外部 ID 字段替代记录 ID 来关联记录。可以在一次调用中创建父子记录(父记录在数组中必须先于子记录,最多 10 层深度,每个类型不同)。

Upsert、Merge 与 Delete 操作

Upsert/Merge/Delete

Upsert(更新或插入):如果记录有 ID 则更新;如果 ID 为 null 或记录不存在则插入。可使用外部 ID 字段作为匹配键。减少代码中的 DML 语句数量,有助于避免达到 Governor Limit。

upsert as user acctsList;
// 或使用外部 ID
upsert as user assets Line_Item_ID__c;

Merge(合并)——仅限 Lead/Contact/Case/Account:将最多 3 条同类型记录合并到主记录中,删除重复记录,重新设定关联记录的父子关系。主记录的字段值(包括 null 和空值)始终覆盖要合并记录的值。只能通过 Database.merge 方法使用。

Delete(删除)——级联删除:删除父对象时,子对象自动被删除(前提是每个子记录都可删除)。删除的记录不会永久删除,而是放入回收站 15 天

Account[] doomedAccts = [SELECT Id, Name FROM Account WHERE Name = 'DotCom'];
try { delete doomedAccts; } catch (DmlException e) { /* 处理异常 */ }

Undelete(恢复):使用 ALL ROWS 关键字查询已删除记录,然后恢复。可恢复因合并而删除的记录(但合并导致的关联重定向无法撤销)。

ConvertLead:仅作为 Database 类方法可用。转换 Lead 为 Account 和 Contact(可选 Opportunity)。

异常处理与 Database 结果对象

异常处理

DML 语句在执行过程中如果数据库出现问题会返回运行时异常——用 try-catch 块包装处理。Database 类方法(设置 partial success 为 false 时)不抛出异常——而是返回包含每个操作状态和错误信息的结果对象:

操作结果类
insert、update、upsertSaveResult / UpsertResult
mergeMergeResult
deleteDeleteResult
undeleteUndeleteResult
convertLeadLeadConvertResult
emptyRecycleBinEmptyRecycleBinResult

DML 选项与事务控制

DML选项

Database.DMLOptions 可以为 insert 和 update 操作指定额外选项:

  • allowFieldTruncation:允许截断过长的 String 值(恢复 API 14.0 之前的行为)
  • assignmentRuleHeader:指定创建 Case 或 Lead 时使用的分配规则(useDefaultRule 或 assignmentRuleId)
  • duplicateRuleHeader.AllowSave:允许保存被识别为重复的记录
  • emailHeader:控制自动响应邮件(triggerAutoResponseEmail)、外部邮件(triggerOtherEmail)和用户邮件(triggerUserEmail)
  • localeOptions:指定返回标签的语言(如 de_DE、en_GB)
  • optAllOrNone:指定操作是否允许部分成功(true=全部回滚,false=成功记录提交)

保存点与事务回滚

保存点

Apex 允许生成保存点(savepoint)——请求中指定数据库状态的点。保存点之后的任何 DML 语句都可以被丢弃,恢复到保存点时的状态。

Account a = new Account(Name = 'xyz');
insert a;
Savepoint sp = Database.setSavepoint();
a.AccountNumber = '123';
update a;
System.areEqual('123', [SELECT AccountNumber FROM Account WHERE Id = :a.Id].AccountNumber);
Database.rollback(sp);  // 回滚到保存点
System.isNull([SELECT AccountNumber FROM Account WHERE Id = :a.Id].AccountNumber);

关键注意事项:设置多个保存点后回滚到非最后一个保存点,之后的保存点变量变为无效。保存点引用不能跨触发器上下文。静态变量不会在回滚时恢复。回滚后 sObject 的 ID 不会被清除。

释放保存点与 Callout:在进行 callout 之前,必须回滚所有未提交的 DML 并使用 Database.releaseSavepoint() 显式释放保存点,否则会抛出 CalloutException。

混合 DML 与 Setup 对象

混合DML

某些 sObject(称为 setup 对象)的 DML 操作不能与非 setup sObject 的 DML 在同一事务中混合——这些对象影响用户对组织记录的访问权限。例如,不能在同一事务中更新 Account 和 UserRole。

受限对象包括:AuthSession、FieldPermissions、Group、GroupMember、ObjectPermissions、PermissionSet、PermissionSetAssignment、QueueSObject、User(有大量限制)、UserRole 等。

解决方案——使用 @future 方法:将对 setup 对象的操作放在 @future 方法中,在单独的事务中执行。在测试方法中,可以使用 System.runAs 块来执行混合 DML 操作。

锁定记录

锁定记录

在 Apex 中,可以使用 FOR UPDATE 在更新 sObject 记录时锁定它们,防止竞态条件和其他线程安全问题。锁在事务完成时释放。

Account[] accts = [SELECT Id FROM Account LIMIT 2 FOR UPDATE];

// 也可在 SOQL for 循环中使用
for (Account[] accts : [SELECT Id FROM Account FOR UPDATE]) {
    // 处理代码
}
不能在使用了 FOR UPDATE 的 SOQL 查询中使用 ORDER BY 关键字。

锁定规则:其他客户端尝试锁定当前被锁定的记录时,最多等待 10 秒后抛出 QueryException。更新被锁定的记录同样最多等待 10 秒后抛出 DmlException。在进行 callout 时,FOR UPDATE 获得的锁定会自动释放。

避免死锁:Apex 运行时引擎首先锁定父记录然后锁子记录,同类型多记录按 ID 顺序锁定。开发者应在应用中从所有位置以相同顺序访问表和行。

关键数据考量

数据考量
  • 必填字段:插入或更新必填字段时必须提供非 null 值
  • String 截断:API 15.0+ 中赋值过长的 String 会产生运行时错误
  • sObject 属性:要执行对应的 DML 操作,sObject 的 createable/updateable/deletable/undeletable 属性必须为 true
  • ID 值:insert 自动设置 ID。插入已有 ID 的记录报错。同一批次中重复 ID 最多处理 5 个
  • 系统字段自动设置:CreatedDate/CreatedById 自动更新,不能显式指定
  • 最大记录数:单次 DML 最多 10,000 条记录
  • 多对象类型创建:可在一次 DML 调用中创建最多 10 种不同类型对象的记录。父记录必须在数组中先于子记录。按对象类型分块提交(最多 10 个块)
  • 批量 DML 异常:allOrNone=true 时全部回滚;allOrNone=false 时进行最多 3 次尝试处理(第 2、3 次尝试时 Governor Limit 被重置)

本章全面涵盖了 Apex 中数据操作的所有核心概念——从 sObject 类型系统到完整的 DML 操作、事务控制、异常处理以及性能和安全最佳实践。