LoginSignup
4
3

More than 1 year has passed since last update.

Salesforce学習記録-Apex非同期

Posted at

非同期Apex機能のまとめ

ここからの抜粋

非同期Apex機能 利用ケース
Future 長時間を要するメソッドがあり、Apex トランザクションの遅延を防止する必要がある場合
外部 Web サービスへのコールアウトを実行する場合
DML 操作を分離して混合保存 DML エラーを回避する場合
Queueable 長時間を要する操作を開始し、その ID を取得する場合
複雑なデータ型をジョブに渡す場合
ジョブをチェーニングする場合
Batchable 大量のデータを処理する長時間のジョブを複数バッチで実行する必要がある場合 (データベースメンテナンスジョブなど)
通常のトランザクションで許容されるよりも大きなクエリ結果が必要になるジョブの場合
Schedulable 特定のスケジュールで実行するために Apex クラスをスケジュールする場合

futureのメソッド

futureの制限

future メソッドを定義するには、future アノテーションを使用してアノテーションを付加します。
メソッドは静的メソッドである必要があり、void 型のみを返します。
外部サービスへのコールアウトを実行するは、コールアウトが許可されることを示す追加パラメータ (callout=true) を利用します。
future メソッドで別の future メソッドを呼び出すことはできません。

global class FutureMethodRecordProcessing
{
    @future(callout=true)
    public static void processRecords(List<ID> recordIds)
    {   
         // Get those records based on the IDs
         List<Account> accts = [SELECT Name FROM Account WHERE Id IN :recordIds];
         // Process records
    }
}

future メソッドのテスト

future アノテーションのあるメソッドをテストするには、startTest()、stopTest() コードブロック内でメソッドを含むクラスをコールします。startTest メソッドの後に実行されたすべての非同期コールはシステムによって収集されます。stopTest を実行する場合、すべての非同期プロセスが同期して実行されます。

@isTest
private class MixedDMLFutureTest {
    @isTest static void test1() {
        User thisUser = [SELECT Id FROM User WHERE Id = :UserInfo.getUserId()];
       // System.runAs() allows mixed DML operations in test context
        System.runAs(thisUser) {
            // startTest/stopTest block to run future method synchronously
            Test.startTest();        
            MixedDMLFuture.useFutureMethod();
            Test.stopTest();
        }
    }
}

キュー

キューメリット

ジョブ ID の取得: System.enqueueJob メソッドを呼び出してジョブを送信すると、メソッドは新しいジョブの ID を返します。この ID は AsyncApexJob レコードの ID に対応します。この ID を使用して、Salesforce ユーザインターフェースの [Apex ジョブ] ページから、またはプログラムで AsyncApexJob のレコードを照会する方法で、ジョブを識別してその進行状況を監視できます
非プリミティブ型の使用: キュー可能クラスには、sObject 型やカスタム Apex 型など、非プリミティブデータ型のメンバー変数を含めることができます。
・ジョブのチェーニング: 実行中のジョブから 2 つ目のジョブを開始することで、2 つのジョブを連鎖的に実行することができます。ジョブのチェーニングは、別の先行プロセスに依存する処理を実行する必要がある場合に便利です。

実装例
public class AsyncExecutionExample implements Queueable {
    public void execute(QueueableContext context) {
        Account a = new Account(Name='Acme',Phone='(415) 555-1212');
        insert a;        
    }
}

//ジョブとしてキューに追加するには、次のメソッドをコールします。
ID jobID = System.enqueueJob(new AsyncExecutionExample());

//AsyncApexJob に対する SOQL クエリを実行し、ジョブ進行状況を照会できます。
AsyncApexJob jobInfo = [SELECT Status,NumberOfErrors FROM AsyncApexJob WHERE Id=:jobID];

キュー可能ジョブのテスト

キュー可能ジョブは、非同期プロセスです。
Test.startTest と Test.stopTest 間のブロック内でキューに送信する必要があります。システムは、テストメソッドで開始されたすべての非同期プロセスを、Test.stopTest ステートメントの後に同期して実行します。

Test例
@isTest
public class AsyncExecutionExampleTest {
    static testmethod void test1() {
        // startTest/stopTest block to force async processes 
        //   to run in the test.
        Test.startTest();        
        System.enqueueJob(new AsyncExecutionExample());
        Test.stopTest();
    }
}

ジョブのチェーニング

別のジョブで他の処理を実行した後にジョブを実行する必要がある場合、キュー可能ジョブをチェーニングできます。
ジョブを別のジョブにチェーニングするには、キュー可能クラスの execute() メソッドから 2 つ目のジョブを送信します。
チェーニングされたジョブの深度に制限はありません。つまり、1 つのジョブから別のジョブにチェーニングし、このプロセスを新しい子ジョブごとに繰り返して新しい子ジョブにリンクできます
ジョブをチェーニングするとき、実行中のジョブから System.enqueueJob で追加できるジョブは 1 つのみです。

実装例
public class AsyncExecutionExample implements Queueable {
    public void execute(QueueableContext context) {
        // Your processing logic here       

        // Chain this job to next job by submitting the next job
        System.enqueueJob(new SecondJob());
    }
}

スケジューラ

特定の時間に実行されるように Apex クラスを呼び出すには、まずクラスに Schedulable インターフェースを実装し、
Salesforce ユーザインターフェースの [Apex をスケジュール] ページ
または System.schedule メソッドのいずれかを使用してスケジュールを指定します。

一度にスケジュールできる Apex ジョブの数は 100 です。
現在の数を確認するには、Salesforce の [スケジュール済みジョブ] ページを表示し、データ型の検索条件を **[スケジュール済み Apex] **にしてカスタムビューを作成します。
また、CronTrigger オブジェクトおよび CronJobDetail オブジェクトをプログラムで照会することで、Apex スケジュール済みジョブの数を取得することもできます。

System.Schedule メソッドは、ジョブの名前、ジョブの実行予定日時を表すために使用する式
Seconds Minutes Hours Day_of_month Month Day_of_week Optional_year
設定詳細は ここ を参照

実装例
global class scheduledMerge implements Schedulable {
   global void execute(SchedulableContext SC) {
      mergeNumbers M = new mergeNumbers(); 
   }
}

//実装よりスケジュール設定
scheduledMerge m = new scheduledMerge();
String sch = '20 30 8 10 2 ?';
String jobID = system.schedule('Merge Job', sch, m);

//クエリを使用したスケジュール済みジョブの進行状況の追跡方法1
CronTrigger ct = 
    [SELECT TimesTriggered, NextFireTime
    FROM CronTrigger WHERE Id = :jobID];

//クエリを使用したスケジュール済みジョブの進行状況の追跡方法2
// SchedulableContext 引数の変数に対して getTriggerId をコールすることで現在のジョブの ID を取得できます。
CronTrigger ct = 
    [SELECT TimesTriggered, NextFireTime
    FROM CronTrigger WHERE Id = :SC.getTriggerId()];

Apex スケジューラのテスト

スケジュールされた Apex をテストするとき、結果に対してテストする前にスケジュール済みジョブが終了している必要があります。System.schedule メソッドを実行する前後で、テストメソッド startTest と stopTest を使用して、テストを続行する前にスケジュール済みジョブが終了するようにします。

テスト用クラス
global class TestScheduledApexFromTestMethod implements Schedulable {

// This test runs a scheduled job at midnight Sept. 3rd. 2022

   public static String CRON_EXP = '0 0 0 3 9 ? 2022';
   
   global void execute(SchedulableContext ctx) {
      CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime
                FROM CronTrigger WHERE Id = :ctx.getTriggerId()];

      System.assertEquals(CRON_EXP, ct.CronExpression);
      System.assertEquals(0, ct.TimesTriggered);
      System.assertEquals('2022-09-03 00:00:00', String.valueOf(ct.NextFireTime));

      Account a = [SELECT Id, Name FROM Account WHERE Name = 
                  'testScheduledApexFromTestMethod'];
      a.name = 'testScheduledApexFromTestMethodUpdated';
      update a;
   }   
}
テストソース
@istest
class TestClass {

   static testmethod void test() {
   Test.startTest();

      Account a = new Account();
      a.Name = 'testScheduledApexFromTestMethod';
      insert a;

      // Schedule the test job

      String jobId = System.schedule('testBasicScheduledApex',
      TestScheduledApexFromTestMethod.CRON_EXP, 
         new TestScheduledApexFromTestMethod());

      // Get the information from the CronTrigger API object
      CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, 
         NextFireTime
         FROM CronTrigger WHERE id = :jobId];

      // Verify the expressions are the same
      System.assertEquals(TestScheduledApexFromTestMethod.CRON_EXP, 
         ct.CronExpression);

      // Verify the job has not run
      System.assertEquals(0, ct.TimesTriggered);

      // Verify the next time the job will run
      System.assertEquals('2022-09-03 00:00:00', 
         String.valueOf(ct.NextFireTime));
      System.assertNotEquals('testScheduledApexFromTestMethodUpdated',
         [SELECT id, name FROM account WHERE id = :a.id].name);

   Test.stopTest();

   System.assertEquals('testScheduledApexFromTestMethodUpdated',
   [SELECT Id, Name FROM Account WHERE Id = :a.Id].Name);
   }
}

一括処理

ここからの抜粋
・Apex の一括処理を使用して、長時間にわたり実行される複雑なプロセスを構築できるようになりました。
・Apex 一括処理は、レコードの小さいバッチに対して動作し、レコードセット全体を管理しやすいチャンクに分割して処理します。
・Apex の一括処理は、インターフェースとして公開され、開発者によって実行される必要があります。一括処理ジョブは実行時に Apex を使用してプログラムで起動できます。
 ・一度に実行できるキュー内または有効な一括処理ジョブは 5 件のみです。Salesforce の [スケジュール済みジョブ] ページを表示するか、プログラムで SOAP API を使用して AsyncApexJob オブジェクトを照会することで、現在のジョブ件数を確認できます。
 ・Apex の一括処理ジョブが実行されると、一括処理ジョブを送信したユーザにメール通知が送信されます。
 ・Apex の一括処理が呼び出されるたびに AsyncApexJob レコードが作成されます。ジョブの状況、エラーの数、進行状況、送信者を取得する SOQL クエリを構成するには、AsyncApexJob レコードの ID を使用します。
 ・クラス内のすべてのメソッドは global または public として定義する必要があります。
 

Database.Batchable インターフェースの実装

start メソッド

インターフェースメソッド execute に渡すレコードまたはオブジェクトを収集するには、
Apex 一括処理ジョブの冒頭でstart メソッドをコールします。
このメソッドは、Database.QueryLocator オブジェクト、
またはジョブに渡すレコードやオブジェクトが含まれる Iterable オブジェクトを返します。

start メソッド
global (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {}

execute メソッド

データの処理単位ごとに必要な処理を実行するには、execute メソッドを使用します。
このメソッドは、メソッドに渡すレコードのバッチごとにコールされます
このメソッドは次を取得します。
Database.BatchableContext オブジェクトへの参照。
List などの sObjects のリストまたはパラメータ化された型のリスト。
Database.QueryLocator を使用している場合は、返されたリストを使用します。

execute メソッド
global void execute(Database.BatchableContext BC, list<P>){}

finish メソッド

確認メールの送信や後処理操作を行う場合に、finish メソッドを使用します。
このメソッドは、すべてのバッチが処理された後にコールされます

finish メソッド
global void finish(Database.BatchableContext BC){}

Apex の一括処理の例

Database.QueryLocator を使用
//バッチクラス
global class UpdateAccountFields implements Database.Batchable<sObject>{
   global final String Query;
   global final String Entity;
   global final String Field;
   global final String Value;

   global UpdateAccountFields(String q, String e, String f, String v){
             Query=q; Entity=e; Field=f;Value=v;
   }
   global Database.QueryLocator start(Database.BatchableContext BC){
      return Database.getQueryLocator(query);
   }
   global void execute(Database.BatchableContext BC, 
                       List<sObject> scope){
      for(Sobject s : scope){s.put(Field,Value); 
      }      update scope;
   }
   global void finish(Database.BatchableContext BC){
   }
}

//バッチ呼び出し
//削除されてまだごみ箱に入っている取引先や請求書を除外するには、この変更したサンプルのように、
//SOQL クエリの WHERE 句に isDeleted=false を付加
String q = 'SELECT Industry FROM Account WHERE isDeleted=false LIMIT 10';
String e = 'Account';
String f = 'Industry';
String v = 'Consulting';
//5のパラメータはexecute メソッドに渡すレコードの数、省略可能。MAX2000 デフォルトは 200
Id batchInstanceId = Database.executeBatch(new UpdateAccountFields(q,e,f,v), 5);

Apex の一括処理でのコールアウトの使用

Database.AllowsCallouts
global class SearchAndReplace implements Database.Batchable<sObject>, 
   Database.AllowsCallouts{
}

Apex の一括処理での状態の使用

クラス定義で Database.Stateful を指定すると、これらのトランザクション間で状態を保持できます。Database.Stateful を使用するとき、インスタンスメンバー変数のみがトランザクション間で値を保持します。静的メンバー変数は、トランザクション間で値を保持せず、リセットされます。

Database.Stateful レコードが処理されるとカスタム項目 total__c が集計されます。
global class SummarizeAccountTotal implements 
    Database.Batchable<sObject>, Database.Stateful{

   global final String Query;
   global integer Summary;
  
   global SummarizeAccountTotal(String q){Query=q;
     Summary = 0;
   }
   global Database.QueryLocator start(Database.BatchableContext BC){
      return Database.getQueryLocator(query);
   }   
   global void execute(
                Database.BatchableContext BC, 
                List<sObject> scope){
      for(sObject s : scope){
         Summary = Integer.valueOf(s.get('total__c'))+Summary;
      }
   }
global void finish(Database.BatchableContext BC){
   }
}

Apex の一括処理のテスト

executeBatch メソッドは、匿名プロセスを開始します。
テストメソッド startTest と stopTest を executeBatch メソッドの前後に使用して、非同期で処理された一括処理ジョブが完了してからテストを続行するようにします。startTest メソッドの後に実行されたすべての非同期コールはシステムによって収集されます。

Database.AllowsCallouts
public static testMethod void testBatch() {
   user u = [SELECT ID, UserName FROM User 
             WHERE username='testuser1@acme.com'];
   user u2 = [SELECT ID, UserName FROM User 
              WHERE username='testuser2@acme.com'];
   String u2id = u2.id;
// Create 200 test accounts - this simulates one execute.  
// Important - the Salesforce.com test framework only allows you to 
// test one execute.  

   List <Account> accns = new List<Account>();
      for(integer i = 0; i<200; i++){
         Account a = new Account(Name='testAccount'+'i', 
                     Ownerid = u.ID); 
         accns.add(a);
      }
   
   insert accns;
   //Start
   Test.StartTest();
   OwnerReassignment reassign = new OwnerReassignment();
   reassign.query='SELECT ID, Name, Ownerid ' +
            'FROM Account ' +
            'WHERE OwnerId=\'' + u.Id + '\'' +
            ' LIMIT 200';
   reassign.email='admin@acme.com';
   reassign.fromUserId = u.Id;
   reassign.toUserId = u2.Id;
   //バッチ実行
   ID batchprocessid = Database.executeBatch(reassign);
   //Stop
   Test.StopTest();

   //実行結果確認
   System.AssertEquals(
           database.countquery('SELECT COUNT()'
              +' FROM Account WHERE OwnerId=\'' + u2.Id + '\''),
           200);  
   
   }
}
4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3