Apex トリガー入門
学習の目的
この単元を完了すると、次のことができるようになります。
- Salesforce オブジェクトのトリガーを作成する。
- トリガーコンテキスト変数を使用する。
- トリガーからクラスメソッドをコールする。
- トリガーで sObject
addError()
メソッドを使用して保存操作を制限する。
始める前に
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
トリガーの例
次の簡単なトリガーは、取引先を挿入する前に実行され、デバッグログにメッセージを書き込みます。
- 開発者コンソールで、[File (ファイル)] | [New (新規)] | [Apex Trigger (Apex トリガー)] をクリックします。
- トリガー名に
HelloWorldTrigger
と入力して、sObject に [Account (取引先)] を選択します。[Submit (送信)] をクリックします。 - デフォルトのコードを次のコードに置き換えます。
trigger HelloWorldTrigger on Account (before insert) { System.debug('Hello World!'); }
- 保存するには、[Ctrl+S] キーを押します。
- トリガーをテストするには、取引先を作成します。
- [Debug (デバッグ)] | [Open Execute Anonymous Window (実行匿名ウィンドウを開く)] をクリックします。
- 新しいウィンドウで、次のコードを追加してから [Execute (実行)] をクリックします。
Account a = new Account(Name='Test Trigger'); insert a;
- デバッグログで、
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'; } }
他の一定のコンテキスト変数は、更新イベントや他の何らかのイベントによってトリガーが実行されたかどうかを示す 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 サービス、または |
isInsert | 挿入操作により、Salesforce ユーザーインターフェース、Apex、または API からこのトリガーが実行された場合に、 |
isUpdate | 更新操作により、Salesforce ユーザーインターフェース、Apex、または API からこのトリガーが実行された場合に、 |
isDelete | 削除操作により、Salesforce ユーザーインターフェース、Apex、または API からこのトリガーが実行された場合に、 |
isBefore | レコードが保存される前にこのトリガーが実行された場合に、 |
isAfter | すべてのレコードが保存された後にこのトリガーが実行された場合に、 |
isUndelete | レコードがごみ箱から復元された後にこのトリガーが実行された場合に、 |
new | 新しいバージョンの sObject レコードのリストを返します。 この sObject リストは |
newMap | 新しいバージョンの sObject レコードへの ID の対応付けです。 この対応付けは |
old | 古いバージョンの sObject レコードのリストを返します。 この sObject リストは |
oldMap | 古いバージョンの sObject レコードへの ID の対応付けです。 この対応付けは |
operationType | 現在の操作に対応する System.TriggerOperation 種別の列挙値を返します。
|
size | 古いバージョンと新しいバージョンの両方を含む、トリガー呼び出しのレコードの合計数。 |
トリガーからのクラスメソッドのコール
トリガーから公開ユーティリティメソッドをコールできます。他のクラスのメソッドをコールすることで、コードを再利用でき、トリガーのサイズが縮小し、Apex コードのメンテナンスが向上します。また、オブジェクト指向プログラミングを利用できるようになります。
次のトリガー例は、トリガーから静的メソッドをコールする方法を示します。挿入イベントによってトリガーが実行された場合、この例では EmailManager
クラスで静的な sendMail()
メソッドをコールします。このユーティリティメソッドは、指定した受信者にメールを送信するもので、挿入された取引先責任者レコードの数を格納します。
- 開発者コンソールで、[File (ファイル)] | [New (新規)] | [Apex Class (Apex クラス)] をクリックします。
EmailManager
と入力して、[OK] をクリックします。- デフォルトのクラス本文を、以下の
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; } }
- 開発者コンソールで、[File (ファイル)] | [New (新規)] | [Apex Trigger (Apex トリガー)] をクリックします。
- トリガー名に
ExampleTrigger
と入力して、sObject に [Contact (取引先責任者)] を選択します。[Submit (送信)] をクリックします。 - デフォルトのコードを次のコードで置き換えて、
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 } }
- 保存するには、[Ctrl+S] キーを押します。
- トリガーをテストするには、取引先責任者を作成します。
- [Debug (デバッグ)] | [Open Execute Anonymous Window (実行匿名ウィンドウを開く)] をクリックします。
- 新しいウィンドウで、次のコードを追加してから [Execute (実行)] をクリックします。
Contact c = new Contact(LastName='Test Contact'); insert c;
- デバッグログで、トリガーが実行されたことを確認します。ログの最後のほうに、ユーティリティメソッドによって書き込まれたデバッグメッセージ (
DEBUG|Email sent successfully
) を見つけます。 - 本文テキストに
1 contact(s) were inserted
と記載されたメールを受信したことを確認します。
新しいトリガーが有効になると、1 件以上の取引先責任者を追加するたびにメールを受信します。
関連レコードの追加
トリガーは多くの場合、トリガーコンテキストのレコード (トリガー起動の原因となったレコード) に関連するレコードのアクセスまたは管理に使用します。
このトリガーは、新しい取引先や更新された取引先にまだ商談が関連付けられていない場合に、各取引先に関連する商談を追加します。トリガーは最初に SOQL クエリを実行して、トリガーが実行された取引先のすべての子商談を取得します。次に、トリガーは Trigger.new
で sObject のリストを反復処理して各取引先の sObject を取得します。取引先に関連する商談 sObject がない場合は、for ループによって 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; } }
- トリガーをテストするには、Salesforce ユーザーインターフェースで取引先を作成して、
Apples & Oranges
という名前を付けます。 - 取引先のページの [商談] 関連リストで、新しい商談を見つけます。トリガーによってこの商談が自動的に追加されています。
トリガーの例外の使用
特定の条件を満たしたときはレコードが保存されないようにするなど、場合によっては特定のデータベース操作に制限を加える必要があることがあります。トリガーにレコードが保存されないようにするには、問題の sObject で addError()
メソッドをコールします。addError()
メソッドは、トリガー内で致命的なエラーを生成します。このエラーメッセージがユーザーインターフェースに表示され、ログに記録されます。
次のトリガーは、取引先に関連する商談がある場合にはその取引先が削除されないようにします。デフォルトでは、取引先が削除されると、その関連するレコードがすべてカスケード削除されます。このトリガーは、商談がカスケード削除されないようにします。このトリガーを試してみましょう。前の例を実行した場合は、組織に Apples & Oranges
という取引先と、関連する商談が存在します。この例ではそのサンプル取引先を使用します。
- 開発者コンソールを使用して、次のトリガーを追加します。
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.'); } }
- Salesforce ユーザーインターフェースで、
Apples & Oranges
取引先のページに移動して、[Delete (削除)] をクリックします。 - 確認ポップアップで、[OK] をクリックします。
Cannot delete account with related opportunities
というカスタムエラーメッセージが付随する入力規則エラーが表示されます。
AccountDeletion
トリガーを無効にします。このトリガーを有効のままにすると、問題をチェックできません。
- [Setup (設定)] から
「Apex Triggers」
(Apex トリガー) を検索します。 - [Apex Triggers (Apex トリガー)] ページで、
AccountDeletion
トリガーの横にある [Edit (編集)] をクリックします。 - [Is Active (有効)] をオフにします。
- [Save (保存)] をクリックします。
トリガーとコールアウト
Apex を使用すると、外部 Web サービスへのコールが可能になり、Apex コードを外部 Web サービスと統合できるようになります。外部 Web サービスへの Apex コールをコールアウトといいます。たとえば、株価情報サービスへのコールアウトを実行して、最新の株価情報を取得できます。トリガーからコールアウトを実行するときは、外部サービスからの応答を待機中に、トリガープロセスによって操作がブロックされないように、コールアウトを非同期に行う必要があります。非同期コールアウトをバックグラウンドプロセスで実行して、外部サービスから応答が返されたら受信します。
トリガーからコールアウトを実行するには、非同期に実行するクラスメソッドをコールします。このようなメソッドは future メソッドと呼ばれ、@future(callout=true)
アノテーションが付加されます。次のクラスの例には、コールアウトを実行する future メソッドが含まれます。
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 を使用したコールアウトの呼び出し」を参照してください。
リソース