Skip to main content

Acionadores do Apex em massa

Objetivos de aprendizagem

Após concluir esta unidade, você estará apto a:
  • Escrever acionadores que operem em coleções de sObjects.
  • Escrever acionadores que executam operações eficientes de SOQL e DML.
Nota

Nota

O desafio prático para esse emblema está traduzido para japonês, espanhol (América Latina) e português (Brasil). Para definir o idioma do seu Trailhead Playground, siga essas instruções. A tradução pode estar desatualizada. Se você não conseguir passar no desafio usando as instruções traduzidas, defina o idioma para inglês e a localidade para Estados Unidos. Em seguida, tente novamente.

Consulte o emblema Trailhead no seu idioma para saber mais sobre como aproveitar a experiência de Trailhead em outros idiomas.

Padrões de design dos acionadores em massa

Os acionadores do Apex são otimizados para funcionar em massa. Recomendamos usar padrões de design em massa para o processamento de registros em acionadores. Usando padrões de design em massa, seus acionadores têm melhor desempenho, desperdiçam menos recursos do servidor e são menos propensos a exceder os limites da plataforma.

O benefício do seu código em massa é que ele pode processar um grande número de registros de forma eficiente e ser executado dentro dos limites de administrador na Lightning Platform. Esses limites de administrador servem para garantir que o código sem controle não monopolize os recursos na plataforma multilocatária.

As seções a seguir demonstram as principais formas de processar em massa seu código do Apex nos acionadores: operando em todos os registros no acionador e realizando SOQL e DML em coleções de sObjects, em vez de sObjects individuais de cada vez. As melhores práticas recomendadas para SOQL e DML em massa aplicam-se a qualquer código do Apex, incluindo SOQL e DML em classes. Os exemplos dados são baseados em acionadores e usam a variável de contexto Trigger.new.

Operando nos conjuntos de registros

Primeiramente, abordaremos o conceito de design em massa mais básico em acionadores. Os acionadores em massa operam em todos os sObjects no contexto do acionador. Normalmente, os acionadores funcionam em um registro caso a ação que disparou o acionador vier da interface de usuário. Mas se a origem da ação for DML em massa ou API, o acionador funciona em um conjunto de registros e não em um só registro. Por exemplo, quando você importa muitos registros pela API, os acionadores funcionam no conjunto de registros completo. Portanto, uma boa prática de programação é sempre assumir que o acionador funciona em uma coleção de registros para que ele funcione em todas as circunstâncias.

O acionador seguinte (MyTriggerNotBulk) assume que apenas um registro fez o acionador disparar. Esse acionador não funciona em um conjunto de registros completo quando vários registros são inseridos na mesma transação. O próximo exemplo apresenta uma versão em massa.

trigger MyTriggerNotBulk on Account(before insert) {
    Account a = Trigger.new[0];
    a.Description = 'New description';
}

Este exemplo (MyTriggerBulk) é uma versão modificada do MyTriggerNotBulk. Ele usa um loop for para iterar sobre todos os sObjects disponíveis. Este loop funcionará se Trigger.new tiver um ou muitos sObjects.

trigger MyTriggerBulk on Account(before insert) {
    for(Account a : Trigger.new) {
        a.Description = 'New description';
    }
}

Executando SOQL em massa

As consultas SOQL podem ser poderosas. É possível recuperar registros relacionados e verificar uma combinação de várias condições em uma única consulta. Usando recursos SOQL, você pode escrever menos códigos e fazer menos consultas ao banco de dados. Fazer menos consultas ao banco de dados ajuda a evitar extrapolar limites de consultas, que são 100 consultas SOQL para Apex síncrono ou 200 para Apex assíncrono.

O acionador a seguir (SoqlTriggerNotBulk) mostra um padrão de consulta SOQL que deve ser evitado. O exemplo faz uma consulta SOQL dentro de um loop for para obter as oportunidades relacionadas de cada conta, que são executadas uma vez para o sObject de cada conta em Trigger.new. Se você tem uma grande lista de contas, uma consulta SOQL dentro de um loop for pode resultar em muitas consultas SOQL. O próximo exemplo mostra a abordagem recomendada.

trigger SoqlTriggerNotBulk on Account(after update) {   
    for(Account a : Trigger.new) {
        // Get child records for each account
        // Inefficient SOQL query as it runs once for each account!
        Opportunity[] opps = [SELECT Id,Name,CloseDate 
                             FROM Opportunity WHERE AccountId=:a.Id];
        // Do some other processing
    }
}

O seguinte exemplo (SoqlTriggerBulk) é uma versão modificada do anterior e mostra a melhor prática para a execução de consultas SOQL. A consulta SOQL faz o trabalho pesado e é chamada uma vez fora do loop principal.

  • A consulta SOQL usa uma consulta interna – (SELECT Id FROM Opportunities) – para obter oportunidades relacionadas das contas.
  • A consulta SOQL está ligada aos registros de contexto do acionador usando a cláusula IN e vinculando a variável Trigger.new na cláusula WHEREWHERE Id IN :Trigger.new. Essa condição WHERE filtra as contas apenas para os registros que dispararam este acionador.

Combinar as duas partes na consulta resulta nos registros que queremos em uma chamada: as contas neste acionador com as oportunidades relacionadas de cada conta.

Após os registros e seus registros relacionados serem obtidos, o loop for itera sobre os registros de interesse usando a variável de coleção, neste caso, acctsWithOpps. A variável de coleção contém os resultados da consulta SOQL. Dessa forma, o loop for itera somente sobre os registros que queremos operar. Como os registros relacionados já foram obtidos, não há a necessidade de mais consultas dentro do loop para obter os registros.

trigger SoqlTriggerBulk on Account(after update) {  
    // Perform SOQL query once.    
    // Get the accounts and their related opportunities.
    List<Account> acctsWithOpps = 
        [SELECT Id,(SELECT Id,Name,CloseDate FROM Opportunities) 
         FROM Account WHERE Id IN :Trigger.new];
    // Iterate over the returned accounts    
    for(Account a : acctsWithOpps) { 
        Opportunity[] relatedOpps = a.Opportunities;  
        // Do some other processing
    }
}

Como alternativa, se você não precisa dos registros da conta pai, pode obter apenas as oportunidades que estejam relacionadas às contas dentro deste contexto de acionador. Esta lista é especificada na cláusula WHERE, combinando o campo AccountId da oportunidade com a ID das contas em Trigger.new: WHERE AccountId IN :Trigger.new. As oportunidades retornadas referem-se a todas as contas neste contexto de acionador e não a uma conta específica. Este próximo exemplo mostra a consulta usada para obter todas as oportunidades relacionadas.

trigger SoqlTriggerBulk on Account(after update) {  
    // Perform SOQL query once.    
    // Get the related opportunities for the accounts in this trigger.
    List<Opportunity> relatedOpps = [SELECT Id,Name,CloseDate FROM Opportunity
        WHERE AccountId IN :Trigger.new];
    // Iterate over the related opportunities    
    for(Opportunity opp : relatedOpps) { 
        // Do some other processing
    }
}

O exemplo anterior pode ser reduzido em tamanho, combinando a consulta SOQL com o loop for em uma única instrução: o loop for SOQL. Apresentamos outra versão deste acionador em massa que usa o loop for SOQL.

trigger SoqlTriggerBulk on Account(after update) {  
    // Perform SOQL query once.    
    // Get the related opportunities for the accounts in this trigger,
    // and iterate over those records.
    for(Opportunity opp : [SELECT Id,Name,CloseDate FROM Opportunity
        WHERE AccountId IN :Trigger.new]) {
        // Do some other processing
    }
}
Nota

Os acionadores são executados em lotes de 200 registros de cada vez. Portanto, se 400 registros disparam um acionador, o acionador dispara duas vezes, uma para cada 200 registros. Por isso, você não tem o benefício de criar lotes de registros de loop for SOQL em acionadores, porque os acionadores também criam lotes de registros. O loop for SOQL é chamado duas vezes neste exemplo, mas uma consulta SOQL independente também seria chamada duas vezes. No entanto, o loop for SOQL ainda parece mais elegante do que a iteração sobre uma variável de coleção!

Executando DML em massa

Ao executar chamadas DML em um acionador ou em uma classe, execute chamadas DML em uma coleção de sObjects, quando possível. Executar a DML em cada sObject individualmente faz uso dos recursos de forma ineficiente. O tempo de execução do Apex permite até 150 chamadas DML em uma transação.

Este acionador (DmlTriggerNotBulk) executa uma chamada de atualização dentro de um loop for que itera sobre oportunidades relacionadas. Se forem respeitadas determinadas condições, o acionador atualiza a descrição da oportunidade. Neste exemplo, a instrução de atualização é chamada, de forma ineficiente, uma vez para cada oportunidade. Se uma operação de atualização de conta em massa disparou o acionador, pode haver muitas contas. Se cada conta tem uma ou duas oportunidades, podemos facilmente terminar com mais de 150 oportunidades. O limite da instrução DML é de 150 chamadas.

trigger DmlTriggerNotBulk on Account(after update) {   
    // Get the related opportunities for the accounts in this trigger.        
    List<Opportunity> relatedOpps = [SELECT Id,Name,Probability FROM Opportunity
        WHERE AccountId IN :Trigger.new];          
    // Iterate over the related opportunities
    for(Opportunity opp : relatedOpps) {      
        // Update the description when probability is greater 
        // than 50% but less than 100% 
        if ((opp.Probability >= 50) && (opp.Probability < 100)) {
            opp.Description = 'New description for opportunity.';
            // Update once for each opportunity -- not efficient!
            update opp;
        }
    }    
}

O exemplo a seguir (DmlTriggerBulk) mostra como realizar DML em massa de maneira eficiente com apenas uma chamada DML em uma lista de oportunidades. O exemplo adiciona o sObject Oportunidade que deseja atualizar a uma lista de oportunidades (oppsToUpdate) do loop. Em seguida, o acionador executa a chamada DML fora do loop nesta lista, depois que todas as oportunidades tiverem sido adicionadas à lista. Este padrão usa apenas uma chamada DML independentemente do número de sObjects que está sendo atualizado.

trigger DmlTriggerBulk on Account(after update) {   
    // Get the related opportunities for the accounts in this trigger.        
    List<Opportunity> relatedOpps = [SELECT Id,Name,Probability FROM Opportunity
        WHERE AccountId IN :Trigger.new];
    List<Opportunity> oppsToUpdate = new List<Opportunity>();
    // Iterate over the related opportunities
    for(Opportunity opp : relatedOpps) {      
        // Update the description when probability is greater 
        // than 50% but less than 100% 
        if ((opp.Probability >= 50) && (opp.Probability < 100)) {
            opp.Description = 'New description for opportunity.';
            oppsToUpdate.add(opp);
        }
    }
    // Perform DML on a collection
    update oppsToUpdate;
}

Aplicaremos os padrões de design que você aprendeu escrevendo um acionador que acessa oportunidades relacionadas das contas. Modifique o exemplo de acionador da unidade anterior para o acionador AddRelatedRecord. O acionador AddRelatedRecord funciona em massa, mas o acionador da unidade anterior não é tão eficiente quanto poderia ser porque ele itera sobre todos os registros sObject Trigger.New . Este próximo exemplo modifica o código do acionador e a consulta SOQL para obter apenas os registros de interesse e, em seguida, iterar sobre esses registros. Se você não tiver criado o acionador, não se preocupe, pode criá-lo nesta seção.

Começaremos com a revisão dos requisitos para o acionador AddRelatedRecord. O acionador dispara após a inserção ou atualização de contas. O acionador adiciona uma oportunidade padrão para cada conta que ainda não tem uma oportunidade. 

Em primeiro lugar, contas recém-inseridas nunca têm uma oportunidade padrão, portanto, definitivamente precisamos adicionar uma. Porém, no caso de contas atualizadas, precisamos determinar se elas têm uma oportunidade relacionada ou não. Portanto, vamos separar como processamos inserções e atualizações usando uma instrução switch na variável de contexto Trigger.operationType. E, em seguida, manter o controle das contas que precisamos processar com uma variável toProcess. Por exemplo:

List<Account> toProcess = null;
switch on Trigger.operationType {
    when AFTER_INSERT {
        // do stuff
    }
    when AFTER_UPDATE {
        // do stuff
    }
}

Para todas as inserções de conta, simplesmente atribuímos as novas contas à lista toProcess:

when AFTER_INSERT {
     toProcess = Trigger.New;
}

Para atualizações, devemos descobrir quais contas existentes neste acionador ainda não têm uma oportunidade relacionada. Como este acionador é um pós-acionador, podemos consultar os registros afetados a partir do banco de dados. Veja a instrução SOQL, cujos resultados atribuímos à lista toProcess.

when AFTER_UPDATE {
     toProcess = [SELECT Id,Name FROM Account
                  WHERE Id IN :Trigger.New AND
                  Id NOT IN (SELECT AccountId FROM Opportunity WHERE AccountId in :Trigger.New)];
}

Agora usamos um loop for para iterar pela lista toProcess de contas e adicionamos uma oportunidade padrão relacionada a oppList. Quando terminarmos, adicionamos em massa a lista de oportunidades usando a instrução DML insert.  Veja como criar ou atualizar o acionador completo.

  1. Se você já tiver criado o acionador AddRelatedRecord na unidade anterior, modifique-o, substituindo o conteúdo pelo acionador seguinte. Caso contrário, adicione o acionador seguinte usando o Developer Console e digite AddRelatedRecord como o nome do acionador.

    trigger AddRelatedRecord on Account(after insert, after update) {
        List<Opportunity> oppList = new List<Opportunity>();
        // Add an opportunity for each account if it doesn't already have one.
        // Iterate over accounts that are in this trigger but that don't have opportunities.
        List<Account> toProcess = null;
        switch on Trigger.operationType {
            when AFTER_INSERT {
            // All inserted Accounts will need the Opportunity, so there is no need to perform the query
                toProcess = Trigger.New;
            }
            when AFTER_UPDATE {
                toProcess = [SELECT Id,Name FROM Account
                             WHERE Id IN :Trigger.New AND
                             Id NOT IN (SELECT AccountId FROM Opportunity WHERE AccountId in :Trigger.New)];
            }
        }
        for (Account a : toProcess) {
            // Add a default opportunity for this account
            oppList.add(new Opportunity(Name=a.Name + ' Opportunity',
                                        StageName='Prospecting',
                                        CloseDate=System.today().addMonths(1),
                                        AccountId=a.Id));
        }
        if (oppList.size() > 0) {
            insert oppList;
        }
    }
  2. Para testar o acionador, crie uma conta na interface de usuário do Salesforce e dê a ela o nome Lions & Cats (Leões e Gatos).
  3. Na lista relacionada de Oportunidades, na página da conta, localize a nova oportunidade Lions & Cats (Leões e Gatos). O acionador adicionou a oportunidade automaticamente!
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