Usar o Apex de lote

Objetivos de aprendizagem

Após concluir esta unidade, você saberá:
  • Onde usar o Apex de lote.
  • Os limites mais elevados do Apex ao usar lotes.
  • A sintaxe do Apex de lote.
  • As melhores práticas para o Apex de lote.

Apex de lote

O Apex de lote é usado para executar trabalhos grandes (da ordem de milhares ou milhões de registros!) que excederiam os limites normais de processamento. Com o Apex de lote, você pode processar registros em lotes de forma assíncrona (daí o nome, “Apex de lote”) para permanecer dentro dos limites da plataforma. Se você tiver um lote de registros para processar, por exemplo, limpeza ou arquivamento de dados, provavelmente, o Apex de lote é sua melhor solução.

Aqui está como o Apex de lote funciona internamente. Digamos que você tenha de processar um milhão de registros usando o Apex de lote. A lógica de execução da classe de lote é chamada uma vez para cada lote de registros que estiver sendo processado. Toda vez que você invoca uma classe de lote, o trabalho é colocado na fila de trabalhos do Apex e é executado como uma transação discreta. Esta funcionalidade tem duas vantagens incríveis:
  • Cada transação começa com um novo conjunto de limites do administrador, o que facilita garantir que seu código permanece dentro de tais limites de execução.
  • Se um lote não for processado com sucesso, as outras transações bem-sucedidas do lote não serão revertidas.

Sintaxe do Apex de lote

Para escrever uma classe do Apex de lote, sua classe tem de implementar a interface Database.Batchable e incluir os seguintes três métodos:

start

Usado para coletar os registros ou objetos a serem passados para o método de interface execute para processamento. Este método é chamado uma vez no início de um trabalho Apex de lote e retorna um objeto Database.QueryLocator ou um Iterável que contém os registros ou objetos passados para o trabalho.

A maioria das vezes, um QueryLocator resolve a questão com uma simples consulta SOQL para gerar o escopo de objetos no trabalho em lote. Mas se você precisar fazer alguma coisa fora do comum, como um loop pelos resultados de uma chamada de API ou pré-processar registros antes de serem passados para o método execute, talvez seja melhor verificar o link dos Iteradores personalizados na seção Recursos.

Com o objeto QueryLocator, o limite do administrador para o número total de registros recuperados pelas consultas SOQL é ignorado e você pode consultar até 50 milhões de registros. Porém, com um Iterável, o limite do administrador para o número total de registros recuperados pelas consultas SOQL ainda está em vigor.

execute

Realiza o processamento real para cada bloco ou “lote” de dados passados para o método. O tamanho do lote padrão é de 200 registros. Não é garantido que os lotes de registros serão executados na sequência em que forem recebidos do método start.

Este método considera o seguinte:
  • Uma referência para o objeto Database.BatchableContext.
  • Uma lista de sObjects, como List<sObject>, ou uma lista de tipos parametrizados. Se você estiver usando um Database.QueryLocator, use a lista retornada.
finish Usado para executar operações de pós-processamento (por exemplo, enviar um email) e chamado uma vez após todos os lotes serem processados.

Aqui está a estrutura de uma classe de Apex de lote:

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

Como invocar uma classe de lote

Para invocar uma classe de lote, basta instanciá-la e chamar Database.executeBatch com a instância:

MyBatchClass myBatchObject = new MyBatchClass();
Id batchId = Database.executeBatch(myBatchObject);

Se preferir, também é possível passar um segundo parâmetro scope para especificar o número de registros que devem ser transmitidos ao método de execução para cada lote. Dica especial: você pode querer limitar o tamanho deste lote se estiver executando dentro dos limites do administrador.

Id batchId = Database.executeBatch(myBatchObject, 100);

Cada invocação do Apex de lote cria um registro AsyncApexJob para que você possa acompanhar o progresso do trabalho. Você pode ver o progresso via SOQL ou gerenciar seu trabalho na fila de trabalhos do Apex. Falaremos sobre a fila de trabalhos em breve.

AsyncApexJob job = [SELECT Id, Status, JobItemsProcessed, TotalJobItems, NumberOfErrors FROM AsyncApexJob WHERE ID = :batchId ];

Como usar o estado no Apex de lote

Normalmente, o Apex de lote não tem estado. Cada execução de um trabalho do Apex de lote é considerada uma operação discreta. Por exemplo, um trabalho do Apex de lote que contenha 1.000 registros e use o tamanho de lote padrão representa cinco transações de 200 registros cada.

Se você especificar Database.Stateful na definição de classe, poderá manter o estado em todas as transações. Quando for usado o Database.Stateful, somente as variáveis de membro da instância reterão seus valores entre as transações. Manter o estado é útil para a contagem ou o resumo dos registros enquanto eles são processados. Em nosso próximo exemplo, atualizaremos os registros de contatos em nosso trabalho de lote e queremos rastrear os registros totais afetados. Assim, podemos incluir a informação no email de notificação.

Amostra de código do Apex de lote

Agora que você já sabe como escrever uma classe do Apex de lote, vamos ver um exemplo prático. Digamos que você tenha um requisito comercial para que todos os contatos empresariais nos EUA tenham o endereço de faturamento de suas empresas principais como endereço de correspondência. Infelizmente, os usuários estão inserindo novos contatos sem os endereços corretos! Os usuários nunca vão aprender?! Escreva uma classe do Apex de lote que garanta que essa exigência é respeitada.

A classe de exemplo a seguir encontra todos os registros de conta que foram passados pelo método start() usando um QueryLocator e atualiza os contatos associados com seus endereços de correspondência da conta. Por fim, ela envia um email com os resultados do trabalho em massa e, por estarmos usando o Database.Stateful para rastrear o estado, com o número de registros atualizado.

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);
    }
}
O código deve ser bastante simples, mas pode ser um pouco abstrato. Aqui está o que acontece com mais detalhes:
  • O método start fornece a coleta de todos os registros que o método execute processará em lotes individuais. Ele retorna a lista de registros a serem processados ao chamar Database.getQueryLocator com uma consulta SOQL. Neste caso, estamos simplesmente consultando todos os registros de Account com o país de faturamento “USA”.
  • Cada lote de 200 registros é transmitido no segundo parâmetro do método execute. O método execute define cada endereço de correspondência do contato para o endereço de faturamento de cada conta e incrementa o recordsProcessed para rastrear o número de registros processados.
  • Quando o trabalho for concluído, o método finish realiza uma consulta no objeto AsyncApexJob (uma tabela que lista informações sobre trabalhos em lote) para obter o status do trabalho, o endereço de email do remetente e algumas outras informações. Em seguida, esse método envia um email de notificação para o remetente do trabalho com informações sobre o trabalho e o número de contatos atualizados.

Como testar o Apex de lote

Uma vez que o desenvolvimento e o teste do Apex são realizados em conjunto, aqui está a forma como testamos a classe de lote acima. Resumindo, inserimos alguns registros, chamamos a classe Apex de lote e confirmamos se os registros foram atualizados com o endereço correto.

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

O método setup insere dez registros de conta com a cidade de faturamento “New York” e o país de faturamento “USA”. Então, para cada conta, ele cria um registro de contato associado. Estes dados são utilizados pela classe de lote.

Nota

Nota

Verifique se o número de registros inseridos é menor ou igual ao tamanho de lote de 200 porque os métodos de teste só podem executar um lote. Você também deve fazer com que o iterável retornado pelo método start corresponda ao tamanho do lote.

No método de teste, a classe de lote UpdateContactAddresses é instanciada, invocada ao chamar Database.executeBatch e transmitida à instância da classe de lote.

A chamada para o Database.executeBatch é incluída no bloco Test.startTest e Test.stopTest. É aqui que a magia acontece. O trabalho é executado após a chamada para o Test.stopTest. Qualquer código assíncrono incluído no Test.startTest e Test.stopTest é executado em sincronia após o Test.stopTest.

Por fim, o teste verifica se todos os registros de contato foram atualizados corretamente verificando se o número de registros de contatos com a cidade de faturamento “New York” corresponde ao número de registros inseridos (isto é, dez).

Melhores práticas

Tal como acontece com métodos futuros, existem algumas coisas que você deve manter em mente ao usar o Apex de lote. Para garantir a rápida execução de trabalhos em lotes, minimize o número de callouts de serviços da Web e ajuste as consultas usadas em seu código do Apex de lote. Quanto mais tempo demorar a execução do trabalho em lote, mais provável será que outros trabalhos serão atrasados quando houver muitos trabalhos na fila. As melhores práticas incluem:
  • Use o Apex de lote somente se você tiver mais de um lote de registros. Caso não tenha registros o bastante para executar mais de um lote, será melhor usar o Apex, pois ele permite a execução em fila.
  • Ajuste todas as consultas SOQL para coletar os registros, para que a execução seja efetuada o mais rápido possível.
  • Minimize o número de solicitações assíncronas criadas para minimizar a possibilidade de atrasos.
  • Tome muito cuidado se estiver planejando invocar um trabalho em lote a partir de um acionador. Você tem de garantir que o acionador não adicionará mais trabalhos em lote do que o limite.

Recursos

Nota

Nota

Lembre-se: este módulo é destinado ao Salesforce Classic. Quando você iniciar sua organização prática, mude para o Salesforce Classic para concluir este desafio.

Continue a aprender de graça!
Inscreva-se em uma conta para continuar.
O que você ganha com isso?
  • Receba recomendações personalizadas para suas metas de carreira
  • Pratique suas habilidades com desafios práticos e testes
  • Monitore e compartilhe seu progresso com os empregadores
  • Conecte-se a orientação e oportunidades de carreira