進行状況の追跡を始めよう
Trailhead のホーム
Trailhead のホーム

Apex トリガの使用開始

学習の目的

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

Apex トリガの記述

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

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

Salesforce ユーザインターフェースのポイント & クリックツールでは行えないタスクをトリガを使用して実行します。たとえば、レコードの項目値の検証や項目の更新を行う場合は、代わりに入力規則やワークフロールールを使用します。

トリガを定義できるのは、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 ループの各取引先に反復処理が行われ、それぞれの [説明] 項目が更新されます。

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() メソッドをコールします。このユーティリティメソッドは、指定した受信者にメールを送信するもので、挿入された取引先責任者レコードの数を格納します。

メモ

メモ

EmailManager クラスは、「Apex の使用開始」単元のクラスの例に含まれます。このトリガを保存する前に、EmailManager クラスを組織に保存して、sendMail() メソッドを静的に変更しておく必要があります。

  1. 開発者コンソールで、[File (ファイル)] | [New (新規)] | [Apex Trigger (Apex トリガ)] をクリックします。
  2. トリガ名に「ExampleTrigger」と入力して、sObject に [Contact (取引先責任者)] を選択します。[Submit (登録)] をクリックします。
  3. デフォルトのコードを次のコードで置き換えて、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
        }
    }
  4. 保存するには、[Ctrl+S] キーを押します。
  5. トリガをテストするには、取引先責任者を作成します。
    1. [Debug (デバッグ)] | [Open Execute Anonymous Window (実行匿名ウィンドウを開く)] をクリックします。
    2. 新しいウィンドウで、次のコードを追加してから [Execute (実行)] をクリックします。
      Contact c = new Contact(LastName='Test Contact');
      insert c;
  6. デバッグログで、トリガが実行されたことを確認します。ログの最後のほうに、ユーティリティメソッドによって書き込まれたデバッグメッセージ (DEBUG|Email sent successfully) を見つけます。
  7. 本文テキストに「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. [設定] から 「Apex トリガ」を検索します。
    2. [Apex トリガ] ページで、AccountDeletion トリガの横にある [編集] をクリックします。
    3. [有効] をオフにします。
    4. [保存] をクリックします。

中級編

トリガで addError() をコールすると、一括 DML のコールが部分的に完了している場合を除いて、一連の操作全体がロールバックされます。

  • Apex の DML ステートメントがトリガを実行する場合、エラーが発生すると操作全体がロールバックされます。ただし、ランタイムエンジンはすべてのレコードを処理して、完全なエラーリストをコンパイルします。
  • Lightning Platform API の一括 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 を使用したコールアウトの呼び出し」を参照してください。

retargeting