Verwenden von Batch-Apex
Lernziele
Nachdem Sie diese Lektion abgeschlossen haben, haben Sie Folgendes gelernt:
- Wann Sie Batch-Apex verwenden sollten
- Die höheren Apex-Grenzen bei Verwendung der Batchverarbeitung
- Batch-Apex-Syntax
- Batch-Apex – bewährte Vorgehensweisen
Mit Trail Together einem Dozenten folgen
Möchten Sie bei diesem Schritt einem Dozenten folgen? Schauen Sie sich dieses Video an, das Teil der Reihe "Trail Together" auf Trailhead Live ist.
(Dieser Clip beginnt bei Minute 31:26, für den Fall, dass Sie zurückspringen und den Anfang des Schritts noch einmal sehen möchten.)
Batch-Apex
Batch-Apex wird für die Ausführung umfangreicher Aufträge (mit Tausenden oder gar Millionen von Datensätzen!) verwendet, die die normalen Verarbeitungsgrenzen sprengen würden. Mit Batch-Apex können Sie Datensätze asynchron in Batches verarbeiten (daher auch – völlig überraschend – der Name "Batch-Apex"), um die Plattformgrenzen einzuhalten. Wenn viele Datensätze verarbeitet werden müssen, z. B. bei der Datenbereinigung oder -archivierung, ist Batch-Apex wahrscheinlich die beste Lösung.
Im Folgenden tauchen wir etwas tiefer in die Funktionsweise von Batch-Apex ein. Nehmen wir einmal an, Sie möchten eine Million Datensätze mit Batch-Apex verarbeiten. Die Ausführungslogik der Batch-Klasse wird ein Mal für jeden verarbeiteten Datensatz-Batch aufgerufen. Bei jedem Aufruf einer Batch-Klasse wird der Auftrag in die Apex-Auftragswarteschlange gestellt und als separate Transaktion ausgeführt. Diese Funktionalität hat zwei riesige Vorteile:
- Jede Transaktion beginnt mit einer neuen Menge an Obergrenzen, wodurch leichter sichergestellt werden kann, dass der Code die Obergrenzen für die Ausführung nicht übersteigt.
- Wird ein Batch nicht erfolgreich verarbeitet, werden nicht auch alle anderen, erfolgreich abgeschlossenen Batch-Transaktionen per Rollback zurückgesetzt.
Batch-Apex-Syntax
Wenn Sie eine Batch-Apex-Klasse schreiben möchten, muss Ihre Klasse die Database.Batchable
-Schnittstelle implementieren und die folgenden drei Methoden enthalten:
start
Dient zum Erfassen der Datensätze oder Objekte, die zur Verarbeitung an die Schnittstellenmethode execute übergeben werden. Diese Methode wird einmal zu Beginn eines Batch-Apex-Auftrags aufgerufen und gibt entweder ein Database.QueryLocator
-Objekt oder eine Iterable
(iterierbare) Komponente zurück, die die an den Auftrag übergebenen Datensätze oder Objekte enthält.
Meist genügt ein QueryLocator
mit einer einfachen SOQL-Abfrage, um den Umfang der Objekte im Batchauftrag zu erzeugen. Falls Sie jedoch etwas Verrücktes tun müssen, beispielsweise die Ergebnisse eines API-Aufrufs in Schleife durchlaufen oder Datensätze vor der Übergabe an die execute-Methode vorverarbeiten, sollten Sie den Link "Custom Iterators" im Abschnitt "Ressourcen" ausprobieren.
Mit dem QueryLocator
-Objekt wird die Obergrenze für die Gesamtzahl der von SOQL-Abfragen abgerufenen Datensätze umgangen und Sie können bis zu 50 Millionen Datensätze abrufen. Bei einer Iterable
(iterierbaren) Komponente wird die Obergrenze für die Gesamtzahl der von SOQL-Abfragen abgerufenen Datensätze durchgesetzt.
execute
Führt die eigentliche Verarbeitung für jeden Block oder “Batch” an Daten durch, der an die Methode übergeben wird. Die standardmäßige Batchgröße beträgt 200 Datensätze. Es kann nicht garantiert werden, dass Datensatz-Batches in der Reihenfolge ausgeführt werden, in der sie von der start-Methode empfangen werden.
Diese Methode verwendet Folgendes:
- Eine Referenz auf das Objekt
Database.BatchableContext
. - Eine Liste mit sObjects wie etwa
List<sObject>
oder eine Liste parametrisierter Typen. Wenn Sie einenDatabase.QueryLocator
nutzen, verwenden Sie die zurückgegebene Liste.
finish
Führt die nach der Verarbeitung folgenden Vorgänge aus (z. B. Senden einer E-Mail) und wird ein Mal aufgerufen, wenn die Verarbeitung aller Batches abgeschlossen ist.
So sieht die Skelettvorlage einer Batch-Apex-Klasse aus:
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 } }
Aufrufen einer Batch-Klasse
Zum Aufrufen einer Batch-Klasse instanziieren Sie diese einfach und rufen dann Database.executeBatch
mit der Instanz auf:
MyBatchClass myBatchObject = new MyBatchClass(); Id batchId = Database.executeBatch(myBatchObject);
Sie können optional auch einen zweiten scope-Parameter übergeben, um die Zahl der Datensätze anzugeben, die an die execute-Methode für jeden Batch übergeben werden soll. Expertentipp: Sie sollten diese Batchgröße begrenzen, wenn Sie in die Nähe von Obergrenzen kommen.
Id batchId = Database.executeBatch(myBatchObject, 100);
Bei jedem Batch-Apex-Aufruf wird ein AsyncApexJob
-Datensatz erstellt, damit Sie den Fortschritt des Auftrags nachverfolgen können. Sie können den Fortschritt über SOQL anzeigen oder Ihren Auftrag in der Apex-Auftragswarteschlange verwalten. Mit der Auftragswarteschlange befassen wir uns in Kürze.
AsyncApexJob job = [SELECT Id, Status, JobItemsProcessed, TotalJobItems, NumberOfErrors FROM AsyncApexJob WHERE ID = :batchId ];
Verwenden des Zustands in Batch-Apex
Batch-Apex ist normalerweise zustandslos. Jede Ausführung eines Batch-Apex-Auftrags gilt als separate Transaktion. Ein Batch-Apex-Auftrag, der 1000 Datensätze umfasst und die standardmäßige Batchgröße verwendet, gilt beispielsweise als fünf Transaktionen mit je 200 Datensätzen.
Wenn Sie in der Klassendefinition Database.Stateful
angeben, können Sie den Zustand über alle Transaktionen hinweg beibehalten. Bei Verwendung von Database.Stateful
behalten nur Instanzmitgliedsvariablen ihre Werte zwischen Transaktionen bei. Die Beibehaltung des Zustands ist nützlich, um Datensätze während der Verarbeitung zu zählen oder zusammenzufassen. In unserem nächsten Beispiel werden wir in unserem Batchauftrag Kontaktdatensätze aktualisieren und möchten die Gesamtzahl der betroffenen Datensätze verfolgen, damit wir diese in der Benachrichtigungs-E-Mail angeben können.
Batch-Apex-Beispielcode
Da Sie jetzt wissen, wie Sie eine Batch-Apex-Klasse schreiben, sehen wir uns nun ein praktisches Beispiel dazu an. Nehmen wir an, in Ihrem Unternehmen lautet eine Geschäftsanforderung, dass bei sämtlichen Kontakten für Firmen in den USA die Rechnungsanschrift der Muttergesellschaft als Postanschrift angegeben werden muss. Leider geben Benutzer neue Kontakte häufig ohne die richtigen Adressen ein! Werden die es je lernen?! Schreiben Sie eine Batch-Apex-Klasse, die sicherstellt, dass die Anforderung durchgesetzt wird.
Die folgende Beispielklasse findet alle Accountdatensätze, die von der start()
-Methode mit einem QueryLocator
übergeben werden und aktualisiert die zugehörigen Kontakte mit der Postanschrift ihres Accounts. Zum Schluss wird eine E-Mail gesendet, die die Ergebnisse des Bulk-Auftrags enthält und, da wir Database.Stateful
zum Verfolgen des Zustands verwenden, auch die Anzahl der geänderten Datensätze angibt.
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); } }
Der Code dürfte kein Problem darstellen, könnte aber etwas abstrakt scheinen. Also, im Einzelnen geschieht Folgendes:
- Die
start()
-Methode stellt die Sammlung aller Datensätze bereit, die von derexecute()
-Methode in einzelnen Batches verarbeitet werden. Die Methode gibt die Liste der zu verarbeitenden Datensätze zurück, indem sieDatabase.getQueryLocator
mit einer SOQL-Abfrage aufruft. In diesem Fall fragen wir einfach alle Accountdatensätze ab, deren "Billing Country (Rechnungsanschrift Land)" auf "USA" festgelegt ist. - Jeder Batch mit 200 Datensätzen wird an den zweiten Parameter der
execute()
-Methode übergeben. Dieexecute()
-Methode stellt die Postanschrift jedes Kontakts auf die Rechnungsadresse des Accounts ein und erhöht den Wert vonrecordsProcessed
, um die Anzahl der verarbeiteten Datensätze zu erfassen. - Nach Abschluss des Auftrags führt die finish-Methode eine Abfrage des
AsyncApexJob
-Objekts (eine Tabelle mit Informationen über Batchaufträge) durch, um den Status des Auftrags, die E-Mail-Adresse des Einreichers und andere Informationen abzurufen. Anschließend wird eine Benachrichtigungs-E-Mail an den Einreicher des Auftrags gesendet, in der die Auftragsinformationen und die Anzahl der geänderten Kontakte angegeben werden.
Testen von Batch-Apex
Da die Entwicklung und das Testen von Apex-Code Hand in Hand gehen, zeigen wir Ihnen nun, wie Sie die obige Batch-Klasse testen. Kurz gesagt: Wir fügen einige Datensätze ein, rufen die Batch-Apex-Klasse auf und prüfen, ob die Datensätze ordnungsgemäß mit der richtigen Adresse aktualisiert wurden.
@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']); } }
Die setup-Methode fügt zehn Accountdatensätze mit "New York" als "Rechnungsanschrift Stadt" und "USA" als "Rechnungsanschrift Land" ein. Dann erstellt sie für jeden Account einen zugehörigen Kontaktdatensatz. Diese Daten werden von der Batch-Klasse verwendet.
In der test()
-Methode wird die Batch-Klasse UpdateContactAddresses
instanziiert und dadurch aufgerufen, dass Database.executeBatch
aufgerufen wird und die Instanz der Batch-Klasse übergeben bekommt.
Der Aufruf von Database.executeBatch
befindet sich innerhalb des Blocks Test.startTest
und Test.stopTest
. Hier ereignen sich all diese wundersamen Dinge. Der Auftrag wird nach dem Aufruf bis Test.stopTest
ausgeführt. Asynchroner Code, der in Test.startTest
und Test.stopTest
enthalten ist, wird nach Test.stopTest
synchron ausgeführt.
Zum Schluss verifiziert der Test, dass alle Kontaktdatensätze ordnungsgemäß geändert wurden. Dazu wird geprüft, ob die Anzahl der Kontaktdatensätze mit "New York" als Stadt der Postanschrift mit der Anzahl der eingefügten Datensätze (also 10) übereinstimmt.
Bewährte Vorgehensweisen
Wie bei future-Methoden, sollten Sie auch bei der Verwendung von Batch-Apex einige Dinge beachten. Damit Batchaufträge möglichst schnell ausgeführt werden, sollten Sie die Dauer von Webservice-Callouts minimieren und in Ihrem Batch-Apex-Code verwendete Abfragen optimieren. Je länger die Ausführung des Batchauftrags dauert, desto wahrscheinlicher werden andere Aufträge in der Warteschlange verzögert, wenn sich viele Aufträge in der Warteschlange befinden. Bewährte Vorgehensweisen:
- Verwenden Sie Batch-Apex nur, wenn Sie über mehr als einen Batch mit Datensätzen verfügen. Falls Ihnen nicht genügend Datensätze vorliegen, um mehr als einen Batch auszuführen, ist es wahrscheinlich besser, wenn Sie Queueable Apex verwenden.
- Optimieren Sie jede SOQL-Abfrage zur Erfassung der Datensätze, damit sie so schnell wie möglich ausgeführt wird.
- Minimieren Sie die Anzahl der erstellten, asynchronen Anforderungen, um Verzögerungen zu vermeiden.
- Seien Sie extrem vorsichtig, wenn Sie vorhaben, einen Batchauftrag von einem Auslöser aus aufzurufen. Sie müssen garantieren können, dass der Auslöser nicht mehr Batchaufträge hinzufügt als laut dem Limit zulässig sind.
Ressourcen
- Apex Developer Guide: Batch-Apex
- Apex Developer Guide: Verwenden von Batch-Apex
- Apex Developer Guide: Custom Iterators
- Apex Developer Guide: TestSetup Annotation