Skip to main content

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
Remarque

Remarque

Vous souhaitez apprendre en français ? Dans ce badge, les validations de défi pratique Trailhead se font en anglais. Les traductions sont fournies entre parenthèses à titre de référence. Dans votre Trailhead Playground, veillez (1) à définir les États-Unis comme région, (2) à sélectionner l’anglais comme langue, et (3) à copier et coller uniquement les valeurs en anglais. Suivez les instructions ici.

Consultez le badge Trailhead dans votre langue pour découvrir comment profiter de l’expérience Trailhead traduite.

Vidéo de démonstration Trail Together

Vous souhaitez être guidé pas à pas pendant que vous travaillez sur cette étape ? Regardez cette vidéo qui fait partie de la série Trail Together sur Trailhead Live.

(Ce clip commence à 31 min 26 s, au cas où vous voudriez revenir en arrière et regarder à nouveau le début de l’étape.)

Apex par lot.

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

Cette méthode est 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 Iterable (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 parcourir les résultats d’un appel API à l’aide d’une boucle ou pré-traiter des enregistrements avant leur transmission à la méthode execute, nous vous recommandons de consulter le lien Itérateurs personnalisés 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 Iterable (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. Il n’est pas garanti que l’exécution des lots d’enregistrements s’effectue dans l’ordre dans lequel ils sont transmis par la méthode start.

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
Cette méthode est utilisée pour exécuter des opérations de post-traitement (par exemple, l’envoi d’un e-mail). Elle est 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 :

public class MyBatchClass implements Database.Batchable<sObject> {
    public (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {
        // collect the batches of records or objects to be passed to execute
    }
    public void execute(Database.BatchableContext bc, List<P> records){
        // process each batch of records
    }
    public 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 correspondant à 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.

public class UpdateContactAddresses implements
    Database.Batchable<sObject>, Database.Stateful {
    // instance member to retain state across transactions
    public Integer recordsProcessed = 0;
    public 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\''
        );
    }
    public 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;
    }
    public 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 allant de pair, 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;
    }
    @isTest static 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 « États-Unis ». Elle crée ensuite un enregistrement de contact associé pour chaque compte. Ces données sont utilisées par la classe par lot.

Remarque

Assurez-vous que le nombre d’enregistrements insérés est inférieur à la taille de lot de 200, car les méthodes de test ne peuvent exécuter qu’un seul lot. Vous devez également vous assurer que l’Iterable (itérable) renvoyé par la méthode start() correspond à la taille du 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 postale « 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.
Formez-vous gratuitement !
Créez un compte pour continuer.
Qu’est-ce que vous y gagnez ?
  • Obtenez des recommandations personnalisées pour vos objectifs de carrière
  • Mettez en pratique vos compétences grâce à des défis pratiques et à des questionnaires
  • Suivez et partagez vos progrès avec des employeurs
  • Découvrez des opportunités de mentorat et de carrière