Apexトリガの実装方法についてまとめていきます。
1.クラス構成と実装構成について
- Apexクラスは、TriggerクラスとHandlerクラスの2クラス構成
- トリガーON/OFFフラグは必要なのでカスタムメタデータに設定すべし(本番リリース後の緊急停止用)
- 再帰処理防止処理は必要な場合は実装すべし(allOrNone=falseの場合に、コード破綻などの懸念があるため任意とする)
2.実行のタイミング
- beforeトリガ:
- レコードがDBに格納される前のタイミングで起動
- afterトリガ:
- レコードがDBに保存された後に起動(※コミット前)
3.使い分け
- beforeトリガ:
- 入力チェック処理
- 項目値の変更処理(newの値を更新)
- afterトリガ:
- id,自動採番項目、システム項目の値が確定してから行う処理
- 他オブジェクトの登録/更新の処理
4.コンテキスト変数
Triggerクラスに用意されている変数。
isExecuting | Apex コードの現在のコンテキストが Visualforce ページ、Web サービス、または executeanonymous() API コールではなく、トリガーである場合、true を返します。 |
isInsert | 挿入操作により、Salesforce ユーザーインターフェース、Apex、または API からこのトリガーが実行された場合に、true を返します。 |
isUpdate | 更新操作により、Salesforce ユーザーインターフェース、Apex、または API からこのトリガーが実行された場合に、true を返します。 |
isDelete | 削除操作により、Salesforce ユーザーインターフェース、Apex、または API からこのトリガーが実行された場合に、true を返します。 |
isBefore | レコードが保存される前にこのトリガーが実行された場合に、true を返します。 |
isAfter | すべてのレコードが保存された後にこのトリガーが実行された場合に、true を返します。 |
isUndelete | レコードがごみ箱から復元された後にこのトリガーが実行された場合に、true を返します。この復元は、Salesforce ユーザーインターフェース、Apex、または API からの復元操作の後にのみ行われます。 |
new | 新しいバージョンの sObject レコードのリストを返します。この sObject リストは insert トリガー、update トリガー、および undelete トリガーでのみ使用でき、レコードは before トリガーでのみ変更できます。 |
newMap | 新しいバージョンの sObject レコードへの ID の対応付けです。この対応付けは before update トリガー、after insert トリガー、after update トリガー、および after undelete トリガーでのみ使用できます。 |
old | 古いバージョンの sObject レコードのリストを返します。この sObject リストは update トリガーと delete トリガーでのみ使用できます。 |
oldMap | 古いバージョンの sObject レコードへの ID の対応付けです。この対応付けは update トリガーと delete トリガーでのみ使用できます。 |
operationType | 現在の操作に対応する System.TriggerOperation 種別の列挙値を返します。 System.TriggerOperation 列挙の可能な値は次のとおりです。BEFORE_INSERT、BEFORE_UPDATE、BEFORE_DELETE、AFTER_INSERT、AFTER_UPDATE、AFTER_DELETE、AFTER_UNDELETE。トリガーの種類に基づいて、異なるプログラミングロジックを使用する場合は、switch ステートメントを使用して、一意のトリガー実行列挙状態の異なる順列を指定することを検討します。 |
size | 古いバージョンと新しいバージョンの両方を含む、トリガー呼び出しのレコードの合計数。 |
- 実行条件の書き方は2通りある
- switch文(operationType)
- IF文(isInsert/isUpdate/isDelete、isBefore/isAfter)
- レコード値を取得するには、Trigger.old、Trigger.oldMap、Trigger.new、Trigger.newMapを使用する。
- 使用可能な変数は、before/afterやinsert/update/deleteで異なるため注意。
- BEFORE_INSERT:new
- AFTER_INSERT:new, newMap
- BEFORE_UPDATE:old, oldMap, new, newMap
- AFTER_UPDATE:old, oldMap, new, newMap
- BEFORE_DELETE:old, oldMap
- AFTER_DELETE:old, oldMap
- AFTER_UNDELETE:new、newMap
- Id、自動採番、CreatedById等のシステム系は、Afterトリガーのnew/newMapでしかアクセスできない。(old/oldMapやBeforeの場合、nullまたは未採番となる)
5.注意事項
- Apexで参照項目をたどることはできない。SOQLにて取得する必要がある。
- 積み上げ集計項目に影響がある場合、Apexトリガーが実行が実行される。
- 無限ループにならないよう注意すること。
6.サンプルソース
Triggerクラス
trigger AccountTrigger on Account(
before insert,
after insert,
before update,
after update,
before delete,
after delete,
after undelete
) {
if (!TriggerSwitch__mdt.getInstance('Account').IsActive__c) {
return; // トリガフラグがOFFの場合はスキップ
}
AccountHandlerTrigger handler = new AccountHandlerTrigger(Trigger.size);
switch on Trigger.operationType {
when BEFORE_INSERT {
handler.onBeforeInsert(Trigger.new);
}
when AFTER_INSERT {
handler.onAfterInsert(Trigger.new, Trigger.newMap);
}
when BEFORE_UPDATE {
handler.onBeforeUpdate(Trigger.old, Trigger.oldMap, Trigger.new, Trigger.newMap);
}
when AFTER_UPDATE {
handler.onAfterUpdate(Trigger.old, Trigger.oldMap, Trigger.new, Trigger.newMap);
}
when BEFORE_DELETE {
handler.onBeforeDelete(Trigger.old, Trigger.oldMap);
}
when AFTER_DELETE {
handler.onAfterDelete(Trigger.old, Trigger.oldMap);
}
when AFTER_UNDELETE {
handler.onUndelete(Trigger.new, Trigger.newMap);
}
}
}
Handlerクラス
public class AccountHandlerTrigger {
private Integer BatchSize = 0;
public AccountHandlerTrigger(Integer size) {
BatchSize = size;
}
public void onBeforeInsert(List<Account> newAccounts) {
// 処理(入力チェック、項目値更新)
for (Account newRecord : newAccounts) {
// 入力チェック
if (String.isNotEmpty(newRecord.BillingPostalCode)) {
Boolean regTest =
Pattern.matches('^[0-9]{3}-[0-9]{4}$', newRecord.BillingPostalCode);
if (!regTest) {
newRecord.BillingPostalCode.addError(
'郵便番号のは000-0000で入力してください。');
}
}
}
}
public void onAfterInsert(List<Account> newAccounts, Map<Id, Account> newAccountMap) {
// 処理(Id、自動採番系、システム日付の値確定後の処理、他オブジェクトの更新処理など)
}
public void onBeforeUpdate(
List<Account> oldAccounts,
Map<Id, Account> oldAccountMap,
List<Account> updatedAccounts,
Map<Id, Account> newAccountMap
) {
// 処理
}
public void onAfterUpdate(
List<Account> oldAccounts,
Map<Id, Account> oldAccountMap,
List<Account> updatedAccounts,
Map<Id, Account> newAccountMap
) {
// 処理
}
public void onBeforeDelete(List<Account> accountsToDelete, Map<Id, Account> oldAccountMap) {
// 処理
}
public void onAfterDelete(List<Account> deletedAccounts, Map<Id, Account> oldAccountMap) {
// 処理
}
public void onUndelete(List<Account> restoredAccounts, Map<Id, Account> newAccountMap) {
// 処理
}
}
テストクラス
@isTest
private class AccountTriggerTest {
@isTest
static void testUserProcess() {
// システム管理者ユーザを新規作成する
String uniqueUserName = 'standarduser' + DateTime.now().getTime() + '@example.com';
Profile sales = [SELECT Id FROM Profile WHERE Name = 'システム管理者'];
User testUser = new User(
Alias = 'test',
Email = 'test@sample.com',
EmailEncodingKey = 'UTF-8',
LastName = 'TestUser',
LanguageLocaleKey = 'ja',
LocaleSidKey = 'ja_JP',
ProfileId = sales.Id,
TimeZoneSidKey = 'Asia/Tokyo',
UserName = uniqueUserName
);
System.runAs(testUser) {
// システム管理者ユーザで実行する
Account acc = new Account(Name = 'テスト');
insert acc;
update acc;
delete acc;
undelete acc;
}
}
}
6.まとめ
Apexトリガの実装方法についてまとめてみました。
- ApexクラスはTriggerクラスとHandlerクラスにより構成
- カスタムメタデータにトリガON/OFFフラグは実装すべし
- 再帰処理は必要に応じて実装すべし
- Beforeトリガなのか、Afterトリガなのかは目的により使い分けるべし
- Id、自動採番、システム日付が必要な場合はAfterトリガを使用すべし
- 実行条件はswitch文(operationType)の方が可読性が高いため利用すべし
コメント