Erfassen Sie Ihre Fortschritte
Trailhead-Startseite
Trailhead-Startseite

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

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 ein Mal zu Beginn eines Batch-Apex-Auftrags aufgerufen und gibt entweder ein Database.QueryLocator-Objekt oder eine 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 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 einen Database.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:

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
    }    

}

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.

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);
    }    

}
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 der execute-Methode in einzelnen Batches verarbeitet werden. Die Methode gibt die Liste der zu verarbeitenden Datensätze zurück, indem sie Database.getQueryLocator mit einer SOQL-Abfrage aufruft. In diesem Fall fragen wir einfach alle Accountdatensätze ab, deren "Rechnungsanschrift Land" auf "USA" eingestellt ist.
  • Jeder Batch mit 200 Datensätzen wird an den zweiten Parameter der execute-Methode übergeben. Die execute-Methode stellt die Postanschrift jedes Kontakts auf die Rechnungsadresse des Accounts ein und erhöht den Wert von recordsProcessed, 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;
    }

    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']);
    }
    
}

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.

Hinweis

Hinweis

Achten Sie darauf, dass die Anzahl eingefügter Datensätze nicht die Batchgröße von 200 übersteigt, da Testmethoden nur einen Batch ausführen können.

In der Testmethode wird die Batch-Klasse UpdateContactAddresses instantiiert 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 Rechnungsanschrift 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

Hinweis

Hinweis

Nicht vergessen: Dieses Modul bezieht sich auf Salesforce Classic. Wenn Sie Ihre Übungs-Organisation starten, wechseln Sie zu Salesforce Classic, um diese Aufgabe abzuschließen.