crmprogrammer38の日記

主にDWHやSalesforceのプログラムメモです。

【Salesforce】ガバナ制限対策 static変数を使ってSOQLの回数を減らそう

Salesforceでは、1つのApex トランザクションでのSOQLの回数制限があります。

Salesforceのstatic変数は、スコープがトランザクションであることを利用してSOQLの発行回数を次の方法で少なくできます。


1.設定情報のオブジェクトへのSOQLを一度で済ます。
2.同じ検索条件の場合に、マスタデータの検索は一度で済ます。


具体的には次です。

1.設定情報のオブジェクトへのSOQLを一度で済ます。

設定情報のオブジェクトはいくつかありますが、次をピックアップします。
(1)レコードタイプ(RecordType)
(2)公開グループやロール、キュー(Group)
(3)現在のユーザ情報(User)


(1)レコードタイプへのSOQLを1度で済ます場合は次のようになります。

public class RecordTypeUtils {

  private static Map<String, RecordType> recTypeMap = new Map<String, RecordType>();
  
  static {
    List<RecordType> rectypelist = [  Select
                   Id
                  ,Name
                  ,DeveloperName
                  ,SobjectType
                  From
                   RecordType
                  where IsActive = true ];
                  
    for( RecordType rectype : rectypelist ){
      String key = rectype.SobjectType + ':' + rectype.DeveloperName;
      
      recTypeMap.put( key, rectype );
    }
  }
  
  public static RecordType getRecordType(String sobjectType , String developerName ){
  
    return recTypeMap.get( sobjectType + ':' + developerName );
  }
}

 使う時は次のようにします。何度使ってもRecordTypeへのSOQLは1度です。

RecordType rectype = RecordTypeUtils.getRecordType('SampleObject__c' , 'SampleRecordType');

 レコードタイプIdをセットしたい時に利用できます。

 

 (2)公開グループやロール、キューへのSOQLを1度で済ます場合は次のようになります。

public class GroupUtils {

  private static Map<String, Group> groupMap = new Map<String, Group>();
  
  static {
    List<Group> grouplist = [  
                  Select
                     Id
                    ,Name
                    ,DeveloperName
                    ,RelatedId
                    ,Type
                  From
                     Group
                ];
                  
    for( Group grp : grouplist ){
      String key = grp.Type + ':' + grp.DeveloperName;
      
      groupMap.put( key, grp );
    }
  }
  
  public static Group getGroup(String type , String developerName ){
  
    return groupMap.get( type + ':' + developerName );
  }
}

 使う時は次のようにします。何度使ってもGroupへのSOQLは1度です。

Group grp = GroupUtils.getGroup('Queue','SampleQueue');

所有者項目に特定のキューをセットしたい時に利用できます。

 

(3) 現在のユーザ情報へのSOQLを1度で済ます場合は次のようになります。

public class CurrentUserUtils {

  @TestVisible
  private static User curuser;

  public static User getCurrentUser(){
  
    if( curuser == null ){
      curuser = [Select
             Id
            ,Name
            ,UserRole.Name
            ,Profile.Name
            ,Contact.Name
            ,Contact.Account.Name
            ,Manager.Name
            ,Manager.email
          From
             User
          Where Id =:Userinfo.getUserId()];
    }
  
    return curuser;
  }
}

 使う時は次のようにします。何度使用してもUser(現在のユーザ)へのSOQLは1度です。

User currentUser = CurrentUserUtils.getCurrentUser();

 現在のユーザ情報を取得する際に利用できます。Apex内のstatic変数「curuser」にTestVisibleアノテーションをつけた意味は、テストクラスの中でユーザを切り替えてテストする際に、ユーザ切り替え時にnullでクリアするためです。

※テストクラス内でSystem.runAs()でユーザを変えてもstatic変数は初期化されません。

そのため、上の処理でcuruserをセットすると、ユーザを変えても動きが正しくならないので、System.runAs()でユーザを変えた後 curuserにnullをセットする必要があります。

 

2.同じ検索条件の場合に、マスタデータの検索は一度で済ます。

検索条件とその結果をstatic変数に保持するようにします。

単純なIdでの検索の場合、次のようになります。

public class SOQLResultUtils  {

  private static Map<String, List<Account>> accCache = new Map<String, List<Account>>();
  
  public static List<Account> getAccount(Set<Id> accIdSet){
  
    if( accIdSet == null ){
      accIdSet = new Set<Id>();
    }
  
    String srchstr = String.valueOf( accIdSet );
  
     if( accCache.keySet().contains( srchstr ) ){
       return accCache.get(srchstr );
     } else {
     
      List<Account> result = [Select
                   Id
                  ,Type
                  ,Phone
                  ,Fax
                  ,Industry
                  From
                   Account
                 ] ;
       accCache.put( srchstr , result );
       return result;
     }
  }
}

Contactのトリガで使った時には次のようになります。検索するIdのSetが同じであれば、SOQLは一度ですみます。

Set<Id> accset = new Set<Id>();
for( Contact c : Trigger.New )
if(c.AccountId != null) accset.add(c.AccountId);
}
List<Account> acclist =
SOQLResultUtils.getAccount(accset);

 同じトリガが何回か動いてしまった場合でも、SOQLの回数は増えません。

 

最後に

トリガのforループの中でSOQLを発行している場合のSOQLの回数制限のエラーはわかりやすく対処しやすいと思います。

 

でも、実際は、そんな簡単なエラーではなく、1つの画面に仕様が集中していたり、1つのオブジェクトにトリガが集中していたりで、SOQLの回数がつみあがった結果、SOQLの回数制限のエラーが発生するケースが多いと思います。

性質が悪いのが、SOQLの回数制限のエラーはある程度開発が進んだ後に発覚するため大きくプログラムを改修すると後退テストの影響範囲が大きくなってしまいます。

 

そんな時に上記のやり方なら、プログラムの改修範囲は大きくないのと、同じようなSOQLなら1つにまとめてしまうことで、さらにSOQLの回数を減らせます。