【Salesforce】Apexトリガの実装方法について

未分類

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または未採番となる)

参考:Apex 開発者ガイド「トリガーコンテキスト変数」

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)の方が可読性が高いため利用すべし

コメント

タイトルとURLをコピーしました