今回はApexトリガ作成時に気をつけることをまとめてみました。
開発時にこうした方がより効率がいいなあとか、かつてよく指摘されたなあということを主観も交えつつ共有できればなと思います。
ご参考程度に。
そもそもApexトリガとは?
本題に入る前に、そもそもApexトリガをあまりよく理解できていない、という方向けにApexトリガはこちらの記事を読んでみてください。
基本のキから解説しています。
気を付けること①:トリガとハンドラーは分ける
トリガ作成する際に気を付けることの1つ目は、
トリガとハンドラー(実行したい処理を記述するクラス)は分けて実装する
ということです。
なぜなら、後に新しい処理を追加する必要が出てきても対応しやすいからです。
例えば以下のトリガにはレコードinsert時にハンドラーの処理を呼び出す処理が書かれています。
このようにすることで、もしinsert時に実行したい処理が増えた場合でも、ハンドラーに処理を追加し(ハンドラー13~15行目)、その処理をbefore Insertメソッド内で呼び出すようにするだけで済みます(ハンドラー6行目)。
trigger AccountTrigger on Account (before insert) {
//Accountが作成された際に、ハンドラーを呼び出す
if(Trigger.isBefore && Trigger.isInsert){
AccountTriggerHandler ah = new AccountTriggerHandler();
ah.beforeInsert(trigger.new);
}
}
public class AccountTriggerHandler {
public void beforeInsert(List<Account> newList){
hogehogo01(newList);
//↓追加した処理
hogehoge02(newList);
}
public void hogehogo01(List<Account> newList){
//処理
}
//↓追加した処理
public void hogehogo02(List<Account> newList){
//処理
}
}
また、エンハンス時に便利なだけでなく可読性も高まるので、個人的にはトリガとハンドラークラスを分けて書くことをオススメします。
人によるかもですが。
気を付けること②: ガバナ制限に気を付ける
トリガに限った話ではありませんが、Apex開発をする時はガバナ制限に注意です。
ガバナ制限は種類がたくさんありどれも気を付ける必要がありますが、とりあえずこれだけは意識して開発しましょうというポイントを挙げておきます。
- ループ内でSOQLを発行しない
- ループ内でDML操作を実行しない
- DML操作を単一レコードで実行しない
- SOQLデータ取得は取得対象を絞る
事項で1つずつ解説します!
1. ループ内でSOQLを発行しない
ガバナ制限では1度の処理(以下1トランザクション)で発行される SOQL クエリの合計数の上限は100と決まっています。(非同期処理では200)
そのため、for文内でSOQLを発行するとすぐにガバナ制限にかかってしまう可能性があります。
for文内ではSOQLの発行は避け、以下のように「SOQL for」を使用するなどしてガバナ制限を回避しましょう。
for(Account acc : [SELECT Id FROM Account]){
//処理
}
2. ループ内でDML操作を実行しない
1トランザクションで発行される DML ステートメント(insert、update、deleteなど)の合計数の上限は150です。(非同期処理も)
こちらもSOQLと同様にfor文内では使用しないようにしましょう。
3. DML操作を単一レコードで実行しない
前述したように1トランザクションで発行される DML ステートメントの合計数には上限があります。
そのため1レコードずつinsertやupdate処理を行っていては、大量データの場合すぐにガバナ制限に底触しています。
対処法としては、以下のコードのように処理対象レコードをListに詰めて一括で処理するようにしましょう。
List<Account> accList = new List<Account>();
for(integer i=0; i<151;i++){
Account acc = new Account();
acc.Name='test'+i;
accList.add(acc);
}
insert accList;
4. SOQLデータ取得は取得対象を絞る
1トランザクションで SOQL クエリによって取得されるレコード合計数の上限は50,000です。(非同期処理も50,000)
案外少ないんです。。。
そのため、それぞれのSOQLで可能な限りレコードの取得対象を絞り、取得件数を抑える必要があります。
例えば、SOQLにWhere句やLimt句を付けることで取得件数を制限することができます。
List<Account> accList = [SELECT Id FROM Account Where Name LIKE 'Sample%' Limit 100];
気を付けること③:beforeトリガとafterトリガの使い分け
Apexトリガには
- beforeトリガ
- afterトリガ
があります。
この2つを使い分けることで、どのタイミングでトリガを起動させ処理を実行するかを決めることができます。
両者の使い分けは次のように理解しておくとよいかと。
- insert/updateされたレコードを、DBに保存される前に操作したい
もしくは、入力規則のチェックより前に処理を実行したい → 「beforeトリガ」 - DBに保存された後のレコードにアクセスしたい → 「afterトリガ」
beforeトリガは、レコードがDBに保存される前なので、「trigger.new」を直接変更することが可能です。
afterトリガは、レコードがDBに保存された後に起動するので、「作成者」や「最終更新日時」などのシステムによって設定される値を利用することができます。
気を付けること④:トリガコンテキストの使い分け
Apexトリガにはトリガコンテキストというものがあります。
簡単にいえば、トリガを効率的に作成することができる非常に便利なアイテムです。
beforeトリガとafterトリガを指定する「isBefore」、「isAfter」もトリガコンテキストです。
他にもトリガが起動する元となったレコードのListである「trigger.new」や「trigger.old」などがあり、場面に応じて使い分けましょう。
気を付けること⑤:処理対象レコードを絞り込む
トリガでは処理対象レコードをできるだけ絞り込むことも大切です。
例えば、取引先オブジェクトの「業種」項目が変更された際にトリガを起動したいとします。
その場合は、「業種」項目が変更されたレコードのみを処理対象とします。
どの項目かにかかわらず更新されたすべての取引先レコードを処理対象とするのは効率がよろしくないわけです。
下のトリガでは「業種」項目が変更されたかどうかを判定するために引数に「trigger.new」と「trigger.oldMap」を指定し、ハンドラーを呼び出しています(6行目)
trigger AccountTrigger on Account (before update) {
//Accountが更新された際に、ハンドラーを呼び出す
if(Trigger.isBefore && Trigger.isUpdate){
AccountTriggerHandler ah = new AccountTriggerHandler();
ah.beforeUpdate(trigger.new, trigger.oldMap);
}
}
ハンドラーでは「業種」項目の値がレコード更新前後で異なるかどうかを判定します。(8行目)
異なる場合は、処理対象リストに加えます。(10行目)
public class AccountTriggerHandler {
public void beforeUpdate(List<Account> newList, Map<Id, Account> oldMap){
//処理対象の取引先レコード格納用リスト
List<Account> targetList = new List<Account>();
for(Account acc : newList){
if(acc.Industry != oldMap.get(acc.Id).Industry){
「業種」項目が変更された場合は、処理対象リストに追加
targetList.add(acc);
}
}
if(!targetList.isEmpty()){
//処理対象リストを引数に処理を呼び出す
hogehogo01(targetList);
}
}
public void hogehogo01(List<Account> newList){
//処理
}
}
このように処理対象を可能な限り絞ることで不要な処理の実行を防ぎ、処理時間の短縮にもつながります。
もちろんガバナ制限の回避にも有効です。
おわりに
今回は、トリガ作成時にとりあえずこれだけは気を付けておこうという点についてまとめてみました。
もちろん他にも注意するべき点は多々あるかと思いますが、最低限この記事でご紹介したことは頭に入れて開発してみてくださいませ。
トリガ関連の記事は他にもありますのでご参考までに。
コメント