Skip to main content

Apex トリガー入門

学習の目的

この単元を完了すると、次のことができるようになります。
  • Salesforce オブジェクトのトリガーを作成する。
  • トリガーコンテキスト変数を使用する。
  • トリガーからクラスメソッドをコールする。
  • トリガーで sObject addError() メソッドを使用して保存操作を制限する。
メモ

メモ

このバッジのハンズオン Challenge は日本語、スペイン語 (LATAM)、ポルトガル語 (ブラジル) に対応しています。Playground の言語を変更するには、こちらの指示に従ってください。日本語等、翻訳された言語と英語に差異がある可能性があります。英語以外の言語での指示に従って Challenge に合格できなかった場合は、[Language (言語)] と [Locale (地域)] をそれぞれ [English (英語)]、[United States (アメリカ合衆国)] に切り替えてからもう一度お試しください。

始める前に

Apex トリガーは楽しく便利で機能的です。このモジュールでは、Apex を使い始められるように基本を説明します。また、他の Salesforce 機能にも言及しながら Apex トリガーの力を紹介します。このモジュールを最大限に活かすには、次のモジュールを先に完了することをお勧めします。

Apex トリガーの作成

Apex トリガーを使用すると、Salesforce のレコードに対するイベント (挿入、更新、削除) の前または後にカスタムアクションを実行できます。データベースシステムでトリガーがサポートされるのと同様に、Apex においてもレコードを管理する目的でトリガーがサポートされています。

一般に、トリガーを使用するのは、特定の条件に基づいて操作を実行する場合、関連レコードを変更する場合、または特定の操作の実行を制限する場合です。SOQL および DML の実行や、カスタム Apex メソッドのコールなど、Apex で行えることはすべてトリガーを使って実行できます。

トリガーを使用するのは、Salesforce ユーザーインターフェースのポイント & クリックツールでは実行できないようなタスクを実行する場合です。たとえば、レコードの項目値の検証や項目の更新を行う場合、入力規則やフローを使用します。パフォーマンスと拡張性を重視する場合、ポイント & クリックツールのロジックが複雑すぎる場合、または CPU 使用率の高い操作を実行する場合は、Apex トリガーを使用します。

トリガーを定義できるのは、Account、Contact といった最上位の標準オブジェクト、カスタムオブジェクト、一部の子標準オブジェクトです。トリガーは作成時にデフォルトで有効になります。Salesforce では、指定したデータベースイベントが発生したときに有効なトリガーが自動的に実行されます。

トリガー構文

トリガー定義の構文は、クラス定義の構文とは異なります。トリガー定義は、trigger キーワードで開始します。その後に、トリガーの名前、トリガーが関連付けられている Salesforce オブジェクト、トリガーを実行する条件が続きます。トリガーの構文は次のとおりです。

trigger TriggerName on ObjectName (trigger_events) {
   code_block
}
挿入、更新、削除、復元操作の前または後にトリガーを実行する場合、カンマ区切りのリストで複数のトリガーイベントを指定します。指定できるのは次のイベントです。
  • before insert
  • before update
  • before delete
  • after insert
  • after update
  • after delete
  • after undelete

トリガーの例

次の簡単なトリガーは、取引先を挿入する前に実行され、デバッグログにメッセージを書き込みます。
  1. 開発者コンソールで、[File (ファイル)] | [New (新規)] | [Apex Trigger (Apex トリガー)] をクリックします。
  2. トリガー名に「HelloWorldTrigger」と入力して、sObject に [Account] を選択します。[Submit (送信)] をクリックします。
  3. デフォルトのコードを次のコードに置き換えます。

    trigger HelloWorldTrigger on Account (before insert) {
    	System.debug('Hello World!');
    }
  4. 保存するには、[Ctrl+S] キーを押します。
  5. トリガーをテストするには、取引先を作成します。
    1. [Debug (デバッグ)] | [Open Execute Anonymous Window (実行匿名ウィンドウを開く)] をクリックします。
    2. 新しいウィンドウで、次のコードを追加してから [Execute (実行)] をクリックします。

      Account a = new Account(Name='Test Trigger');
      insert a;
  6. デバッグログで、Hello World! ステートメントを見つけます。ログには、トリガーが実行されたことも示されます。

トリガーの種類

トリガーには次の 2 種類があります。

  • before トリガーは、レコードがデータベースに保存される前にレコードの値を更新または検証する場合に使用します。
  • after トリガーは、システムによって設定された項目値 (レコードの Id 項目や LastModifiedDate 項目など) にアクセスする場合や、他のレコードの変更に影響を与える場合に使用します。after トリガーを実行するレコードは参照のみです。

コンテキスト変数の使用

トリガーを実行するレコードにアクセスするには、コンテキスト変数を使用します。たとえば、Trigger.new には、挿入または更新トリガーで挿入されたすべてのレコードが含まれます。Trigger.old には、更新トリガーで更新される前の sObject の旧バージョン、または削除トリガーで削除された sObject のリストがあります。1 つのレコードが挿入された場合や、API または Apex を使用して多数のレコードが一括して挿入され場合にトリガーが実行されます。したがって、Trigger.new などのコンテキスト変数には、1 つのレコードが含まれることもあれば、複数のレコードが含まれることもあります。Trigger.new に反復処理を行うと、個々の sObject を取得できます。

次の例は、HelloWorldTrigger のサンプルトリガーを変更したものです。for ループの各取引先に反復処理が行われ、それぞれの Description ([説明]) 項目が更新されます。

trigger HelloWorldTrigger on Account (before insert) {
    for(Account a : Trigger.new) {
        a.Description = 'New description';
    }   
}
メモ

before トリガーを実行したレコードは、トリガーの実行が終了した後にシステムによって保存されます。DML の挿入または更新操作を明示的にコールせずに、トリガーのレコードを変更できます。これらのレコードに DML ステートメントを実行すると、エラーが表示されます。

他の一定のコンテキスト変数は、更新イベントや他の何らかのイベントによってトリガーが実行されたかどうかを示す Boolean 値を返します。これらの変数は、トリガーで複数のイベントを組み合わせるときに役立ちます。次に例を示します。

trigger ContextExampleTrigger on Account (before insert, after insert, after delete) {
    if (Trigger.isInsert) {
        if (Trigger.isBefore) {
            // Process before insert
        } else if (Trigger.isAfter) {
            // Process after insert
        }        
    }
    else if (Trigger.isDelete) {
        // Process after delete
    }
}

次の表は、トリガーに使用可能なすべてのコンテキスト変数の包括的なリストです。

変数 使用方法
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_INSERTBEFORE_UPDATEBEFORE_DELETEAFTER_INSERTAFTER_UPDATEAFTER_DELETEAFTER_UNDELETE です。トリガーの種類に基づいて異なるプログラミングロジックを使用する場合は、switch ステートメントを使用して、一意のトリガー実行列挙状態の異なる順列を指定することを検討してください。

size 古いバージョンと新しいバージョンの両方を含む、トリガー呼び出しのレコードの合計数。

トリガーからのクラスメソッドのコール

トリガーから公開ユーティリティメソッドをコールできます。他のクラスのメソッドをコールすることで、コードを再利用でき、トリガーのサイズが縮小し、Apex コードのメンテナンスが向上します。また、オブジェクト指向プログラミングを利用できるようになります。

次のトリガー例は、トリガーから静的メソッドをコールする方法を示します。挿入イベントによってトリガーが実行された場合、この例では EmailManager クラスで静的な sendMail() メソッドをコールします。このユーティリティメソッドは、指定した受信者にメールを送信するもので、挿入された取引先責任者レコードの数を格納します。

  1. 開発者コンソールで、[File (ファイル)] | [New (新規)] | [Apex Class (Apex クラス)] をクリックします。
  2. EmailManager と入力して、[OK] をクリックします。
  3. デフォルトのクラス本文を、以下の EmailManager クラスの例で置き換えます。
    public class EmailManager {
        // Public method
        public static void sendMail(String address, String subject, String body) {
            // Create an email message object
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
            String[] toAddresses = new String[] {address};
            mail.setToAddresses(toAddresses);
            mail.setSubject(subject);
            mail.setPlainTextBody(body);
            // Pass this email message to the built-in sendEmail method 
            // of the Messaging class
            Messaging.SendEmailResult[] results = Messaging.sendEmail(
                                      new Messaging.SingleEmailMessage[] { mail });
            // Call a helper method to inspect the returned results
            inspectResults(results);
        }
        // Helper method
        private static Boolean inspectResults(Messaging.SendEmailResult[] results) {
            Boolean sendResult = true;
            // sendEmail returns an array of result objects.
            // Iterate through the list to inspect results. 
            // In this class, the methods send only one email, 
            // so we should have only one result.
            for (Messaging.SendEmailResult res : results) {
                if (res.isSuccess()) {
                    System.debug('Email sent successfully');
                }
                else {
                    sendResult = false;
                    System.debug('The following errors occurred: ' + res.getErrors());                 
                }
            }
            return sendResult;
        }
    }
  4. 開発者コンソールで、[File (ファイル)] | [New (新規)] | [Apex Trigger (Apex トリガー)] をクリックします。
  5. トリガー名に「ExampleTrigger」と入力して、sObject に [Contact (取引先責任者)] を選択します。[Submit (送信)] をクリックします。
  6. デフォルトのコードを次のコードで置き換えて、sendMail() のメールアドレスのプレースホルダーテキストを自身のメールアドレスに変更します。
    trigger ExampleTrigger on Contact (after insert, after delete) {
        if (Trigger.isInsert) {
            Integer recordCount = Trigger.new.size();
            // Call a utility method from another class
            EmailManager.sendMail('Your email address', 'Trailhead Trigger Tutorial', 
                        recordCount + ' contact(s) were inserted.');
        }
        else if (Trigger.isDelete) {
            // Process after delete
        }
    }
  7. 保存するには、[Ctrl+S] キーを押します。
  8. トリガーをテストするには、取引先責任者を作成します。
    1. [Debug (デバッグ)] | [Open Execute Anonymous Window (実行匿名ウィンドウを開く)] をクリックします。
    2. 新しいウィンドウで、次のコードを追加してから [Execute (実行)] をクリックします。
      Contact c = new Contact(LastName='Test Contact');
      insert c;
  9. デバッグログで、トリガーが実行されたことを確認します。ログの最後のほうに、ユーティリティメソッドによって書き込まれたデバッグメッセージ (DEBUG|Email sent successfully) を見つけます。
  10. 本文テキストに 1 contact(s) were inserted と記載されたメールを受信したことを確認します。

    新しいトリガーが有効になると、1 件以上の取引先責任者を追加するたびにメールを受信します。

トリガーは多くの場合、トリガーコンテキストのレコード (トリガー起動の原因となったレコード) に関連するレコードのアクセスまたは管理に使用します。

このトリガーは、新しい取引先や更新された取引先にまだ商談が関連付けられていない場合に、各取引先に関連する商談を追加します。トリガーは最初に SOQL クエリを実行して、トリガーが実行された取引先のすべての子商談を取得します。次に、トリガーは Trigger.new で sObject のリストを反復処理して各取引先の sObject を取得します。取引先に関連する商談 sObject がない場合は、for ループによって 1 件が作成されます。トリガーで新しい商談が作成された場合、最後のステートメントでその商談が挿入されます。

  1. 開発者コンソールを使用して次のトリガーを追加します (HelloWorldTrigger の例の手順に従いますが、トリガー名には AddRelatedRecord を使用します)。
    trigger AddRelatedRecord on Account(after insert, after update) {
        List<Opportunity> oppList = new List<Opportunity>();
        // Get the related opportunities for the accounts in this trigger
        Map<Id,Account> acctsWithOpps = new Map<Id,Account>(
            [SELECT Id,(SELECT Id FROM Opportunities) FROM Account WHERE Id IN :Trigger.new]);
        // Add an opportunity for each account if it doesn't already have one.
        // Iterate through each account.
        for(Account a : Trigger.new) {
            System.debug('acctsWithOpps.get(a.Id).Opportunities.size()=' + acctsWithOpps.get(a.Id).Opportunities.size());
            // Check if the account already has a related opportunity.
            if (acctsWithOpps.get(a.Id).Opportunities.size() == 0) {
                // If it doesn't, add a default opportunity
                oppList.add(new Opportunity(Name=a.Name + ' Opportunity',
                                           StageName='Prospecting',
                                           CloseDate=System.today().addMonths(1),
                                           AccountId=a.Id));
            }           
        }
        if (oppList.size() > 0) {
            insert oppList;
        }
    }
  2. トリガーをテストするには、Salesforce ユーザーインターフェースで取引先を作成して、「Apples & Oranges」という名前を付けます。
  3. 取引先のページの [商談] 関連リストで、新しい商談を見つけます。トリガーによってこの商談が自動的に追加されています。
メモ

追加したトリガーが、トリガーコンテキストに属するすべてのレコードに反復処理を行います。つまり、for ループが Trigger.new を反復処理します。ただし、このトリガーのループはもっと効率化できます。実際にアクセスする必要があるのは、このトリガーコンテキストの全取引先ではなく、そのサブセット (商談のない取引先) のみです。次の単元では、このトリガーをさらに効率化する方法を説明します。「一括トリガーの設計パターン」単元では、SOQL クエリを変更して商談のない取引先のみを取得する方法を学習します。その後で、それらのレコードのみを反復処理する方法を学習します。

トリガーの例外の使用

特定の条件を満たしたときはレコードが保存されないようにするなど、場合によっては特定のデータベース操作に制限を加える必要があることがあります。トリガーにレコードが保存されないようにするには、問題の sObject で addError() メソッドをコールします。addError() メソッドは、トリガー内で致命的なエラーを生成します。このエラーメッセージがユーザーインターフェースに表示され、ログに記録されます。

次のトリガーは、取引先に関連する商談がある場合にはその取引先が削除されないようにします。デフォルトでは、取引先が削除されると、その関連するレコードがすべてカスケード削除されます。このトリガーは、商談がカスケード削除されないようにします。このトリガーを試してみましょう。前の例を実行した場合は、組織に Apples & Oranges という取引先と、関連する商談が存在します。この例ではそのサンプル取引先を使用します。

  1. 開発者コンソールを使用して、次のトリガーを追加します。
    trigger AccountDeletion on Account (before delete) {
        // Prevent the deletion of accounts if they have related opportunities.
        for (Account a : [SELECT Id FROM Account
                         WHERE Id IN (SELECT AccountId FROM Opportunity) AND
                         Id IN :Trigger.old]) {
            Trigger.oldMap.get(a.Id).addError(
                'Cannot delete account with related opportunities.');
        }
    }
  2. Salesforce ユーザーインターフェースで、Apples & Oranges 取引先のページに移動して、[削除] をクリックします。
  3. 確認ポップアップで、[OK] をクリックします。

    Cannot delete account with related opportunities. というカスタムエラーメッセージが付随する入力規則エラーが表示されます。

  4. AccountDeletion トリガーを無効にします。このトリガーを有効のままにすると、問題をチェックできません。
    1. [Setup (設定)] から 「Apex Triggers 」 (Apex トリガー) を検索します。
    2. [Apex Triggers (Apex トリガー)] ページで、AccountDeletion トリガーの横にある [Edit (編集)] をクリックします。
    3. [Is Active (有効)] をオフにします。
    4. [Save (保存)] をクリックします。
メモ
トリガーで addError() をコールすると、一括 DML のコールが部分的に完了している場合を除いて、一連の操作全体がロールバックされます。
  • Lightning Platform API の一括 DML コールがトリガーを実行すると、ランタイムエンジンは不正なレコードを除外します。次に、ランタイムエンジンは、エラーが発生しなかったレコードのみを保存しようとします。
  • Apex の DML ステートメントがトリガーを実行する場合、エラーが発生すると操作全体がロールバックされます。ただし、ランタイムエンジンはすべてのレコードを処理して、完全なエラーリストをコンパイルします。

トリガーとコールアウト

Apex を使用すると、外部 Web サービスへのコールが可能になり、Apex コードを外部 Web サービスと統合できるようになります。外部 Web サービスへの Apex コールをコールアウトといいます。たとえば、株価情報サービスへのコールアウトを実行して、最新の株価情報を取得できます。トリガーからコールアウトを実行するときは、外部サービスからの応答を待機中に、トリガープロセスによって操作がブロックされないように、コールアウトを非同期に行う必要があります。非同期コールアウトをバックグラウンドプロセスで実行して、外部サービスから応答が返されたら受信します。

トリガーからコールアウトを実行するには、非同期に実行するクラスメソッドをコールします。このようなメソッドは future メソッドと呼ばれ、@future(callout=true) アノテーションが付加されます。次のクラスの例には、コールアウトを実行する future メソッドが含まれます。

メモ

この例では、説明目的で架空のエンドポイント URL を使用しています。したがって、エンドポイントを有効な URL に変更して、Salesforce にエンドポイントのリモートサイトを追加しない限り、この例を実行することはできません。

public class CalloutClass {
    @future(callout=true)
    public static void makeCallout() {
        HttpRequest request = new HttpRequest();
        // Set the endpoint URL.
        String endpoint = 'http://yourHost/yourService';
        request.setEndPoint(endpoint);
        // Set the HTTP verb to GET.
        request.setMethod('GET');
        // Send the HTTP request and get the response.
        HttpResponse response = new HTTP().send(request);
    }
}

次の例は、クラスでメソッドをコールして、コールアウトを非同期で実行するトリガーを示しています。

trigger CalloutTrigger on Account (before insert, before update) {
    CalloutClass.makeCallout();
}

このセクションは、コールアウトの概要のみを示し、詳述はいたしません。詳細は、『Apex 開発者ガイド』の「Apex を使用したコールアウトの呼び出し」を参照してください。

無料で学習を続けましょう!
続けるにはアカウントにサインアップしてください。
サインアップすると次のような機能が利用できるようになります。
  • 各自のキャリア目標に合わせてパーソナライズされたおすすめが表示される
  • ハンズオン Challenge やテストでスキルを練習できる
  • 進捗状況を追跡して上司と共有できる
  • メンターやキャリアチャンスと繋がることができる