Verwenden von Batch-Apex
Lernziele
Nachdem Sie diese Lektion abgeschlossen haben, haben Sie Folgendes gelernt:
- Batch-Apex-Syntax
- Batch-Apex – bewährte Vorgehensweisen
Mit Trail Together einem Dozenten folgen
Möchten Sie bei diesem Schritt einem Dozenten folgen?
(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.
Diese Funktionalität hat zwei riesige Vorteile:
- Wird ein Batch nicht erfolgreich verarbeitet, werden nicht auch alle anderen, erfolgreich abgeschlossenen Batch-Transaktionen per Rollback zurückgesetzt.
Batch-Apex-Syntax
Meist genügt ein QueryLocator mit einer einfachen SOQL-Abfrage, um den Umfang der Objekte im Batchauftrag zu erzeugen.
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.
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. Die standardmäßige Batchgröße beträgt 200 Datensätze.
- Eine Referenz auf das Objekt
Database.BatchableContext - Eine Liste mit sObjects wie etwa
List<sObject>oder eine Liste parametrisierter Typen. Wenn Sie einenDatabase.QueryLocatornutzen, verwenden Sie die zurückgegebene Liste.
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.
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. Expertentipp: Sie sollten diese Batchgröße begrenzen, wenn Sie in die Nähe von Obergrenzen kommen.
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.
Batch-Apex-Beispielcode
Wenn Sie in der Klassendefinition Database.Stateful angeben, können Sie den Zustand über alle Transaktionen hinweg beibehalten. 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! 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.
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.
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);
}
}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.getQueryLocatormit 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.
@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
Assert.areEqual(10, [select count() from contact where MailingCity = 'New York']);
}
}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. Dann erstellt sie für jeden Account einen zugehörigen Kontaktdatensatz. Diese Daten werden von der Batch-Klasse verwendet.
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. 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
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.
Bewährte Vorgehensweisen:
- 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
