Apexを開発していると様々なエラーに遭遇します。
本記事では日常的に出会いがちなエラーメッセージとそれぞれの原因、対処法について紹介します。
※ 初級から上級までの判定は、処理の実装頻度と遭遇しやすさを基準にして独断で設定しています。
【初級編①】System.QueryException: List has no rows for assignment to SObject
エラーの状況
SOQLクエリが結果を返さなかったときに発生します。
通常、SObjectに直接クエリ結果を割り当てる際に発生します。
// エラー例 該当するレコードが存在しない場合、このエラーが発生します。
Account acc = [SELECT Id, Name FROM Account WHERE Name = 'NonExistentName'];
対処法
クエリ結果が存在するかどうかを確認してからSObjectに割り当てるようにします。
// 修正例
List<Account> accList = [SELECT Id, Name FROM Account WHERE Name = 'ExistentName'];
if (!accList.isEmpty()) { // レコードが存在する場合
Account acc = accList[0];
} else {
// レコードが存在しない場合の処理を記述
}
SOQLの結果をリストに格納し、レコードが返ってこなかった場合の処理を実装する、またはレコードが返ってきた時のみ処理が実行されるように記載するなどしてエラーを回避しましょう。
【初級編②】System.LimitException: Apex CPU time limit exceeded
エラーの状況
Apexコードの実行が許可されるCPU時間を超えたときに発生します。
このエラーは、コードの実行時間が制限を超えた場合に表示されます。
// エラー例
for(Integer i = 0; i < 100000; i++) {
// 重い処理
}
対処法
コードの効率化を図ってループの中で重い処理を行わないようにする、そして重い処理は非同期処理として実装することが重要です。
// 修正例
for (処理オブジェクト rec :recs) {
// 条件でレコードを絞る
if (rec.field = '条件') {
// 処理
}
}
重い処理を関数化し、条件で処理対象のレコードを絞り込むなどして必要最低限の回数で呼び出すようにしてください。
ApexのCPU時間のガバナ制限は同期処理で10,000ミリ秒=10秒、非同期処理で60,000ミリ秒=60秒です。
大量のデータを処理する場合や重い処理が必要な場合はApexバッチや@futureメソッドに置き換えるなど、業務要件に応じて非同期処理の実装を検討してください。
【中級編】System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out
エラーの状況
DML操作後にコミットやロールバックせずにHTTPコールアウトを実行しようとしたときに発生します。
// エラー例
update accounts; // DML操作
HttpRequest req = new HttpRequest();
Http http = new Http();
HttpResponse res = http.send(req); // DMLの後でHTTPコールアウトを実行
対処法
DML操作をHTTPコールアウトの前にコミットまたはロールバックするようにします。
// 修正例
HttpRequest req = new HttpRequest();
Http http = new Http();
HttpResponse res = http.send(req);
update accounts; // HTTPリクエストの後にDML操作を実行
【上級編】System.DmlException: Insert failed. First exception on row 0; first error: MIXED_DML_OPERATION
エラーの状況
エラーメッセージ全体は以下のようになっています。
System.DmlException: Insert failed. First exception on row 0; first error: MIXED_DML_OPERATION, 非設定オブジェクトを更新した後の設定オブジェクト上の DML 操作 (またはその逆) は、許可されていません: PermissionSetAssignment、元のオブジェクト: Account: []
このエラーメッセージは、非設定オブジェクトと設定オブジェクトを同じトランザクション内で操作しようとしたときに発生します。
Salesforceでは、例えば権限セットの割り当てレコード(設定オブジェクト)と取引先オブジェクト(非設定オブジェクト)のように、設定オブジェクトと非設定オブジェクトの間でDML操作を混在させることは許可されていません。
この制約は、データの整合性を保つために設けられています。
// エラー例
public static void ngMethod() {
Account a = new Account(Name = 'サンプル取引先');
insert a; // 非設定オブジェクトのDML
List<PermissionSet> permSets = [SELECT Id FROM PermissionSet WHERE Name = 'SalesUserPsl'];
List<User> userRecs = [SELECT Id From User];
PermissionSetAssignment assign = new PermissionSetAssignment();
assign.AssigneeId = userRecs[0].Id;
assign.PermissionSetId = permSets[0].Id;
insert assign; // 設定オブジェクトのDML
}
Apexで公開グループや権限セットを割り当てるような設定オブジェクトを操作する処理の実装以外に、テストクラス内でテストデータとテストユーザを作成する際にも発生しがちです。
対処法
設定オブジェクトを操作する部分を非同期処理で実装するなど、非設定オブジェクトと設定オブジェクトのDMLのトランザクションを分けることで解決します。
// 修正例
public class exampleClass { // 同期処理クラス
public static void okMethod() {
Account a = new Account(Name = 'サンプル取引先');
insert a; // 非設定オブジェクトのDML
UtilExamClass.futureExam(); // 非同期処理クラスの@futureメソッドを呼び出す
}
}
public class UtilExamClass { // 非同期処理クラス
@future
public static void furureExam() {
List<PermissionSet> permSets = [SELECT Id FROM PermissionSet WHERE Name = 'SalesUserPsl'];
List<User> userRecs = [SELECT Id From User];
PermissionSetAssignment assign = new PermissionSetAssignment();
assign.AssigneeId = userRecs[0].Id;
assign.PermissionSetId = permSets[0].Id;
insert assign; // 設定オブジェクトのDML
}
}
テストクラスの場合はいずれかをTestSetupメソッドでDML操作する、System.RunAsを使うなどで解決できます。
// 修正例
@isTest
public with sharing class testAccountCreate {
@TestSetup
static void makeData(){
List<PermissionSet> perSets = [SELECT Id FROM PermissionSet WHERE Name = 'SalesUserPsl'];
List<User> userRecs = [SELECT Id From User];
PermissionSetAssignment assign = new PermissionSetAssignment();
assign.AssigneeId = userRecs[0].Id;
assign.PermissionSetId = perSets[0].Id;
insert assign; // 設定オブジェクトのDML操作
System.runAs(userRecs[0]) {
Account a = new Account(Name = 'テスト取引先1');
insert a; // OK:非設定オブジェクトのDML操作
}
}
@isTest
static void createAccountTest() {
Account a = new Account(Name = 'テスト取引先2');
insert a; // OK:非設定オブジェクトのDML操作
}
}
まとめ
以上がApex開発をするうえでよく遭遇するエラーメッセージとその対処法です。
これ以外にもApex実装にはたくさんの落とし穴がありますが、エラーメッセージと対処法を理解することで、より安定した、効率的なApexコードを実装できるようになります。
少しでも参考になれば幸いです。