Suivez votre progression
Accueil Trailhead
Accueil Trailhead

Utilisation d'Apex par lot

Objectifs de formation

Dans cette unité, vous allez découvrir :
  • L’emplacement d’utilisation d’un code Apex par lot
  • Les limitations Apex lors de l’utilisation d’un lot
  • La syntaxe Apex par lot
  • Les meilleures pratiques Apex par lot

Batch Apex

Un code Apex par lot est utilisé pour exécuter les tâches volumineuses (des milliers ou des millions d’enregistrements !) qui dépasseraient les limitations de traitement normales. L’utilisation d’un code Apex par lot permet de traiter les enregistrements de façon asynchrone dans des lots (d’où le nom « Apex par lot ») afin de respecter les limites de la plate-forme. Si vous avez un grand nombre d’enregistrements à traiter, par exemple, des données de mise à jour ou d’archivage, l’utilisation d’un code Apex par lot est probablement la meilleure solution.

Voici comment fonctionne un code Apex par lot. Supposons que vous souhaitez traiter 1 million d’enregistrements en utilisant un code Apex par lot. La logique d’exécution de la classe par lot est appelée pour chaque lot d’enregistrements que vous traitez. Chaque fois que vous invoquez une classe par lot, la tâche est placée dans la file d’attente des tâches Apex et exécutée en tant que transaction discrète. Cette fonctionnalité offre deux avantages importants :
  • Chaque transaction initie une nouvelle série de limitations, ce qui aide votre code à respecter les limites d’exécution du gouverneur.
  • Si le traitement d’un lot échoue, les autres transactions par lot réussies ne sont pas annulées.

Syntaxe d'Apex par lot

Pour écrire une classe Apex par lot, votre classe doit implémenter l’interface Database.Batchable et inclure les trois méthodes suivantes :

start

Utilisée pour collecter les enregistrements ou les objets à transmettre pour traitement à la méthode d’interface execute. Cette méthode est appelée une fois au début d’une tâche Apex par lot. Elle renvoie un objet Database.QueryLocator ou un itérable qui contient les enregistrements ou les objets transmis à la tâche.

La plupart du temps, un QueryLocator suffit avec une simple requête SOQL qui génère l’étendue des objets dans la tâche par lot. Cependant, si vous devez exécuter une tâche inhabituelle, par exemple itérer sur les résultats d’un appel API ou pré-traiter les enregistrements avant leur transmission à la méthode execute, nous recommandons de suivre le lien Custom Iterators dans la section Ressources.

Avec l’objet QueryLocator, les limitations du gouverneur en nombre total d’enregistrements récupérés par les requêtes SOQL sont contournées, et vous pouvez interroger jusqu’à 50 millions d’enregistrements. Néanmoins, avec un itérable, les limitations du gouverneur en nombre total d’enregistrements récupérés par les requêtes SOQL restent applicables.

execute

Exécutez le traitement pour chaque segment ou « lot » de données transmis à la méthode. La taille de lot par défaut est de 200 enregistrements. L’exécution des lots d’enregistrements dans l’ordre dans lequel ils sont transmis par la méthode start n’est pas garantie.

Cette méthode utilise les éléments suivants :
  • Une référence à l’objet Database.BatchableContext.
  • Une liste de sObjects, par exemple List<sObject>, ou une liste de types paramétrés. Si vous utilisez un Database.QueryLocator, utilisez la liste renvoyée.
finish
Utilisée pour exécuter des opérations de post traitement (par exemple, l’envoi d’un e-mail) et appelée lorsque tous les lots ont été traités.

La structure de base d’une classe Apex par lot se présente comme suit :

global class MyBatchClass implements Database.Batchable<sObject> {

    global (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {
        // collect the batches of records or objects to be passed to execute
    }

    global void execute(Database.BatchableContext bc, List<P> records){
        // process each batch of records
    }    

    global void finish(Database.BatchableContext bc){
        // execute any post-processing operations
    }    

}

Invocation d’une classe par lot

Pour invoquer une classe par lot, il suffit de l’instancier, puis d’appeler Database.executeBatch avec l’instance :

MyBatchClass myBatchObject = new MyBatchClass(); 
Id batchId = Database.executeBatch(myBatchObject);

Vous pouvez également transmettre un deuxième paramètre facultatif, scope, afin de spécifier le nombre d’enregistrements qui doivent être transmis dans la méthode execute de chaque lot. Conseil pro : limitez ce lot pour ne pas atteindre les limitations du gouverneur.

Id batchId = Database.executeBatch(myBatchObject, 100);

Chaque invocation du code Apex par lot crée un enregistrement AsyncApexJob qui permet de suivre la progression de la tâche. Vous pouvez afficher la progression via SOQL ou gérer votre tâche dans une file d’attente des tâches Apex. Nous reviendrons sur la file d’attente des tâches plus loin.

AsyncApexJob job = [SELECT Id, Status, JobItemsProcessed, TotalJobItems, NumberOfErrors FROM AsyncApexJob WHERE ID = :batchId ];

Utilisation de l’état dans Apex par lot

Généralement, un code Apex par lot n’a pas d’état. Chaque exécution d’une tâche Apex par lot est considérée comme une transaction discrète. Par exemple, une tâche Apex par lot qui contient 1000 enregistrements et qui utilise la taille de lot par défaut est considérée comme cinq transactions de 200 enregistrements chacune.

Si vous spécifiez Database.Stateful dans la définition de la classe, vous pouvez conserver l’état à travers toutes les transactions successives. Lors de l’utilisation de Database.Stateful, seules les variables membres d’instance conservent leur valeur entre les transactions. La gestion de l’état est utile pour compter ou résumer les enregistrements à mesure qu’ils sont traités. Dans notre prochain exemple, nous allons mettre à jour des enregistrements de contact dans notre tâche par lot. Nous souhaitons suivre le nombre total d’enregistrements affectés pour pouvoir l’inclure dans l’e-mail de notification.

Exemple de code Apex par lot

Vous savez désormais comment écrire une classe Apex par lot. Prenons un exemple pratique. Supposons que votre cahier des charges exige que tous les contacts de sociétés aux États-Unis utilisent l’adresse de facturation de la société mère dans le champ Adresse postale. Malheureusement, les utilisateurs saisissent les nouveaux contacts sans renseigner la bonne adresse ! Comment leur rappeler ? Écrivez une classe Apex par lot pour vous assurer que cette exigence est respectée.

La classe exemple ci-dessous recherche tous les enregistrements de compte transmis par la méthode start() à l’aide d’un QueryLocator et met à jour les contacts associés avec l’adresse postale de leur compte. Pour terminer, elle envoie un e-mail avec les résultats de la tâche par lot et, comme nous utilisons Database.Stateful pour suivre l’état, le nombre d’enregistrements mis à jour.

global class UpdateContactAddresses implements 
    Database.Batchable<sObject>, Database.Stateful {
    
    // instance member to retain state across transactions
    global Integer recordsProcessed = 0;

    global Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator(
            'SELECT ID, BillingStreet, BillingCity, BillingState, ' +
            'BillingPostalCode, (SELECT ID, MailingStreet, MailingCity, ' +
            'MailingState, MailingPostalCode FROM Contacts) FROM Account ' + 
            'Where BillingCountry = \'USA\''
        );
    }

    global void execute(Database.BatchableContext bc, List<Account> scope){
        // process each batch of records
        List<Contact> contacts = new List<Contact>();
        for (Account account : scope) {
            for (Contact contact : account.contacts) {
                contact.MailingStreet = account.BillingStreet;
                contact.MailingCity = account.BillingCity;
                contact.MailingState = account.BillingState;
                contact.MailingPostalCode = account.BillingPostalCode;
                // add contact to list to be updated
                contacts.add(contact);
                // increment the instance member counter
                recordsProcessed = recordsProcessed + 1;
            }
        }
        update contacts;
    }    

    global void finish(Database.BatchableContext bc){
        System.debug(recordsProcessed + ' records processed. Shazam!');
        AsyncApexJob job = [SELECT Id, Status, NumberOfErrors, 
            JobItemsProcessed,
            TotalJobItems, CreatedBy.Email
            FROM AsyncApexJob
            WHERE Id = :bc.getJobId()];
        // call some utility to send email
        EmailUtils.sendMessage(job, recordsProcessed);
    }    

}
Le code devrait être relativement simple, mais en réalité il peut s’avérer abstrait. Détaillons le code :
  • La méthode start fournit la collection de tous les enregistrements que la méthode execute doit exécuter dans des lots individuels. Elle renvoie la liste des enregistrements à traiter en appelant Database.getQueryLocator avec une requête SOQL. Dans le cas présent, nous interrogeons simplement tous les enregistrements Compte dans lesquels Pays de facturation comprend « USA ».
  • Chaque lot de 200 enregistrements est transmis au deuxième paramètre de la méthode execute. La méthode execute définit l’adresse postale de chaque contact sur l’adresse postale du compte, et incrémente recordsProcessed pour suivre le nombre d’enregistrements traités.
  • Une fois la tâche terminée, la méthode finish exécute une requête sur l’objet AsyncApexJob (un tableau qui regroupe les informations sur les tâches par lot) en vue de récupérer le statut de la tâche, l’adresse e-mail de l’émetteur et d’autres informations. Elle envoie ensuite un e-mail de notification à l’émetteur de la tâche comprenant les informations sur la tâche et le nombre de contacts mis à jour.

Test d’un Apex par lot

Le développement et le test de code Apex vont de pair. Par conséquent, examinons comment tester la classe par lot ci-dessus. En résumé, nous insérons quelques enregistrements, appelés classe Apex par lot, puis nous affirmons que les enregistrements ont été correctement mis à jour avec l’adresse appropriée.

@isTest
private class UpdateContactAddressesTest {

    @testSetup 
    static void setup() {
        List<Account> accounts = new List<Account>();
        List<Contact> contacts = new List<Contact>();
        // insert 10 accounts
        for (Integer i=0;i<10;i++) {
            accounts.add(new Account(name='Account '+i, 
                billingcity='New York', billingcountry='USA'));
        }
        insert accounts;
        // find the account just inserted. add contact for each
        for (Account account : [select id from account]) {
            contacts.add(new Contact(firstname='first', 
                lastname='last', accountId=account.id));
        }
        insert contacts;
    }

    static testmethod void test() {        
        Test.startTest();
        UpdateContactAddresses uca = new UpdateContactAddresses();
        Id batchId = Database.executeBatch(uca);
        Test.stopTest();

        // after the testing stops, assert records were updated properly
        System.assertEquals(10, [select count() from contact where MailingCity = 'New York']);
    }
    
}

La méthode setup insère 10 enregistrements de compte comprenant la ville de facturation « New York » et le pays de facturation « USA ». Elle crée ensuite un enregistrement de contact associé pour chaque compte. Ces données sont utilisées par la classe par lot.

Remarque

Remarque

Assurez-vous que le nombre d’enregistrements insérés est inférieur à la taille de lot de 200, car les méthodes test ne peuvent exécuter qu’un seul lot.

Dans la méthode test, la classe par lot UpdateContactAddresses est instanciée, invoquée en appelant Database.executeBatch et en lui transmettant l’instance de la classe par lot.

L’appel à Database.executeBatch est inclus dans le bloc Test.startTest et Test.stopTest. C’est ici que la magie opère. La tâche est exécutée après l’appel à Test.stopTest. Tout code asynchrone inclus entre Test.startTest et Test.stopTest est exécuté de façon synchrone après Test.stopTest.

Pour terminer, le test vérifie que tous les enregistrements de contact ont été correctement mis à jour en vérifiant que le nombre d’enregistrements de contacts comprenant la ville de facturation « New York » correspond au nombre d’enregistrements insérés (c’est-à-dire 10).

Meilleures pratiques

Comme avec des méthodes futures, lors de l’utilisation d’un code Apex par lot, vous devez tenir compte de quelques points. Pour permettre l’exécution rapide des tâches par lots, réduisez les délais des appels externes de service Web et ajustez les requêtes utilisées dans votre code Apex par lot. Lorsque la file d’attente contient de nombreuses tâches, plus l’exécution d’une tâche par lot est longue, plus vous risquez de retarder les autres tâches. Appliquez les meilleures pratiques :
  • Utilisez un code Apex par lot uniquement si vous avez plusieurs lots d’enregistrements. Si vous n’avez pas suffisamment d’enregistrements pour exécuter plusieurs lots, il est préférable d’utiliser un code Apex pour file d'attente.
  • Ajustez toutes les requêtes SOQL afin de regrouper aussi rapidement que possible les enregistrements à exécuter.
  • Réduisez le nombre de requêtes asynchrones créées afin de réduire les risques de délai.
  • Si vous envisagez d’invoquer une tâche par lot à partir d’un déclencheur, soyez extrêmement prudent(e). Assurez-vous que le nombre de tâches par lot ajoutées par le déclencheur ne dépasse pas la limite.

Ressources

Remarque

Remarque

N’oubliez pas que ce module est conçu pour Salesforce Classic. Lorsque vous lancez votre organisation d’exercice, basculez vers Salesforce Classic pour relever ce défi.