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
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 unDatabase.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 :
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éthodeexecute()
exécutera dans des lots individuels. Elle renvoie la liste des enregistrements à traiter en appelantDatabase.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éthodeexecute()
définit l’adresse postale de chaque contact sur l’adresse postale du compte, et incrémenterecordsProcessed
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.
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.
Ressources
- Apex Developer Guide: Apex par lot.
- Apex Developer Guide: Using Batch Apex
- Apex Developer Guide: Custom Iterators
- Apex Developer Guide: Annotation TestSetup