+ Start a Discussion
Anto HotelbedsAnto Hotelbeds 

Passing list of Ids to apex batch

Hi,

 

I have a problem trying to create my first apex batch.

 

I have got a trigger, that checks if an account has been modified. If so, I add the id of the account to a list and at pass the list of ids to the apex batch.

 

The apex batch must call to a function in a class that will make a call out.

 

This is my trigger:

 

trigger ModAccountInvokeService on Account (after update) {
    List <Id> MainAccounts=new List <Id>();
    List <Id> BranchAccounts=new List <Id>();
    for (Integer i=0;i<Trigger.new.size();i++){
        Account acc=Trigger.new[i];
        if (acc.RecordTypeId=='012D000000035RH' || acc.RecordTypeId=='012D000000035Bw'){
            if (Trigger.new[i].Province_Commercial_2__c!=Trigger.old[i].Province_Commercial_2__c){
                //
                //myCallOuts.SyncMainAccount(acc.Id);
                //Introduce the Id of the account to the list
                MainAccounts.add(Trigger.new[i].Id);
            }
        
    }
    //Llamo a los batches que irán haciendo el call out
     Database.executeBatch(new MainAccountSynchBratch(MainAccounts),1);
}

 And this is my Batch Code:

 

global class MainAccountSynchBatch implements Database.Batchable<Id>,Database.AllowsCallouts{
    
    //Recibo el listado de las cuentas main que hay que sincronizar
    
	List<Id> MainAccounts = new List<Id>();
    global MainAccountSynchBatch(List<Id> listMainAccounts)
	{
        MainAccounts = listMainAccounts;
    }

    global List<Id> start(Database.BatchableContext BC)
	{
		//return DataBase.getQueryLocator([SELECT Id FROM account WHERE Id IN : MainAccounts]);
        return Database.Batchable();//(MainAcounts);
	}
	
    global void execute(Database.BatchableContext BC,List<Account> scopeAcc)
	{
		
        for (Integer i=0;i<scopeAcc.size();i++){
            //scopeAcc.get(i).Commercial_Area__c=ownerMap.get(scopeAcc.get(i).OwnerId).Commercial_Area__c;
            myCallOuts.SynchMainAccount(scopeAcc.get(i));
    	}
    	//update scopeAcc;
	}
    
    global void finish(Database.BatchableContext BC)
	{
    	//Send an email to the User after your batch completes
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
		String[] toAddresses = new String[] {'a.tejado@hotelbeds.com'};
		mail.setToAddresses(toAddresses);
		mail.setSubject('Apex Batch Synch Job is done');
		mail.setPlainTextBody('The batch Apex Synch job processed ');
		Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
	}

}

 How can I pass to the Execute method the list of Ids that I receive in the batch?

 

Really appreciate your help.

 

Thanks,

 

Antonio

Best Answer chosen by Admin (Salesforce Developers) 
Jerun JoseJerun Jose

Glad to hear that you got your code working. But it is a lot safer to have the callouts made using the @future methods than with the batch apex. The reason for this is because you cannot have more than 5 instances of the same batch class executing in your org.

 

To help you understand, try uploading 6 batches of data using apex data loader in a single operation. This will cause the apex trigger to run 6 times which will eventually try to create 6 instance of the batch apex class. You will see that the last batch execution will not happen and the 6th load batch will error out.

 

Because of the large volume of data that you want to integrate, I think that the option we have is to have a daily sync of the account data. You can have your apex trigger to mark the records that need to be synced with the external system. Consider using a flag checkbox that will be set to true by the trigger. Now you can use a schedulable apex class to call a batch apex which will query for the flagged accounts and then use callouts to sync that data with the external system.

 

I think with this approach, you can cut down on the number of callouts made, as even though there are 10000 updates you wouldnt be communicating every update to the external system, rather only the last update on that day. But as you may now realize, the time lag might be an issue. To improve the time lag, you could reduce the schedule to have the batch apex job fired on an hourly basis is you want. But batch apex itself has some limitations. Read through the governor limits documentation to see how exactly they might impact your case.

 

One last note: Any apex transaction cannot make more than 10 callouts, so your batch apex will need to be 'scoped' to make sure that the execute method does not make more than 10 callouts in a transaction.

All Answers

Jerun JoseJerun Jose

I may be totally wrong here, but were you able to save the batch apex? The reason I am asking is becase you have a batch apex which implements the Database.Batchable<Id> but your execute method runs on List<Account> scopeAcc (which as far as I know will not compile). Also, the start method is not returning a querylocator which is again odd. I will look into this again sometime later, but in the meantime if you can comment on these.

Anto HotelbedsAnto Hotelbeds

Hi,

 

I am really lost here....I hadnt saved my batch before, just trying someone to guide me a bit....

 

Now I manage to save it but I think Im doing something wrong because it doesnt make a lot of sense.

I want to pass from my trigger to the batch a list of Ids of accounts that were modified. And then from the batch, call to a method(passing to it the id) that will make a call out to other system.

 

This is the batch:

 

global class MainAccountSynchBatch implements Database.Batchable<sObject>,Database.AllowsCallouts{
    
    //Recibo el listado de las cuentas main que hay que sincronizar
    
	global final List<Id> MainAccounts = new List<Id>();
    
    global MainAccountSynchBatch(List<Id> listMainAccounts)
	{
        MainAccounts = listMainAccounts;
    }

    global Database.QueryLocator start(Database.BatchableContext BC)
	{
		return DataBase.getQueryLocator([SELECT Id FROM account WHERE Id IN: MainAccounts]);
        //return MainAcounts;
	}
	
    global void execute(Database.BatchableContext BC,List<sObject> scopeAcc)
	{
		
        for (Integer i=0;i<scopeAcc.size();i++){
            //scopeAcc.get(i).Commercial_Area__c=ownerMap.get(scopeAcc.get(i).OwnerId).Commercial_Area__c;
            myCallOuts.SyncMainAccount(scopeAcc.get(i).Id);
    	}
    	//update scopeAcc;
	}
    
    global void finish(Database.BatchableContext BC)
	{
    	//Send an email to the User after your batch completes
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
		String[] toAddresses = new String[] {'a.tejado@hotelbeds.com'};
		mail.setToAddresses(toAddresses);
		mail.setSubject('Apex Batch Synch Job is done');
		mail.setPlainTextBody('The batch Apex Synch job processed ');
		Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
	}

}

 I have problems with the start method. I dont know if passing to the batch a list of ids or a list of accounts.

 

this is the trigger:

 

trigger ModAccountInvokeService on Account (after update) {
    List <Account> MainAccounts=new List <Account>();
    for (Integer i=0;i<Trigger.new.size();i++){
        Account acc=Trigger.new[i];
        if (acc.RecordTypeId=='012D000000035RH' || acc.RecordTypeId=='012D000000035Bw'){
            if (Trigger.new[i].Province_Commercial_2__c!=Trigger.old[i].Province_Commercial_2__c){
                //
                //myCallOuts.SyncMainAccount(acc.Id);
                //Introduce the Id or account to the list
                MainAccounts.add(Trigger.new[i]);
                // MainAccounts.add(Trigger.new[i].Id);
            }
    }
    //I call the batch HOW?????
    //Database.executeBatch(MainAccountSynchBatch(MainAccounts));
}

 Thanks a lot for your help, I have been for a couple of hours with this and didnt move forward at all

 

Antonio

Jerun JoseJerun Jose

The updated code looks a lot better. I actually cannot see anything wrong with it. What exactly is the problem you are facing now?

Anto HotelbedsAnto Hotelbeds

Hi Jerun,

 

Thanks a lot for your help.

 

I think the problem is solved but I wanted to expose to you my general use case. Maybe you could give me some light and help me to decide whether this is the best solution or not.

 

My users will make modifications to accounts and these modifications have to be applied in our company operational system. I make this using an http call out. Before thinking about the salesforce limits, I had the call out implemented inside a @future class that was called from the trigger.

 

Due to the fact that maybe in one day even more than 10000 accounts can be modified, i was afraid(almost sure) I could reach the 200hundred future call per day per user limit. So I decided to change all the future methods and do what you can see in this post: make a call to the apex batch from the trigger, and the apex batch  calls the class that wil make the call out.

 

Is this a good solution? Is there any other better solution?

 

These is the final code from the trigger and the apex batch in case it might help someone:

 

TRIGGER:

trigger ModAccountInvokeService on Account (after update) {
    List <Account> MainAccounts=new List <Account>();
    //Map <Id,Account> MainAccountsmap=new Map <Id,Account>();
    List <Id> BranchAccounts=new List <Id>();
    for (Integer i=0;i<Trigger.new.size();i++){
        Account acc=Trigger.new[i];
        if (acc.RecordTypeId=='012D000000035RH' || acc.RecordTypeId=='012D000000035Bw'){
            if (Trigger.new[i].Country_Commercial__c!=Trigger.old[i].Country_Commercial__c||Trigger.new[i].Province_Commercial_2__c!=Trigger.old[i].Province_Commercial_2__c){
                //
                //myCallOuts.SyncMainAccount(acc.Id);
                //Aqui meto el ID de la cuenta en una lista
                MainAccounts.add(Trigger.new[i]);
                //MainAccountsmap.put(Trigger.new[i].Id,Trigger.new[i]);
            }
        }
    }
    //Call the batch
    Database.executeBatch(new MainAccountSynchBatch(MainAccounts));
}

 

global class MainAccountSynchBatch implements Database.Batchable<sObject>,Database.AllowsCallouts{
    
    //Recibo el listado de las cuentas main que hay que sincronizar
    
	global List<sObject> MainAccounts = new List<sObject>();
    
    global MainAccountSynchBatch(List<sObject> listMainAccounts)
	{
        MainAccounts = listMainAccounts;
    }

    global Database.QueryLocator start(Database.BatchableContext BC)
	{
		return DataBase.getQueryLocator([SELECT Id FROM account WHERE Id IN: MainAccounts]);
        //return MainAcounts;
	}
	
    global void execute(Database.BatchableContext BC,List<sObject> scopeAcc)
	{
		
        for (Integer i=0;i<scopeAcc.size();i++){
            //scopeAcc.get(i).Commercial_Area__c=ownerMap.get(scopeAcc.get(i).OwnerId).Commercial_Area__c;
            myCallOuts.SyncMainAccount(scopeAcc.get(i).Id);
    	}
    	//update scopeAcc;
	}
    
    global void finish(Database.BatchableContext BC)
	{
    	//Send an email to the User after your batch completes
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
		String[] toAddresses = new String[] {'a.tejado@hotelbeds.com'};
		mail.setToAddresses(toAddresses);
		mail.setSubject('Apex Batch Synch Job is done');
		mail.setPlainTextBody('The batch Apex Synch job processed ');
		Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
	}

}

 

 

Thanks,

 

Antonio

Jerun JoseJerun Jose

Glad to hear that you got your code working. But it is a lot safer to have the callouts made using the @future methods than with the batch apex. The reason for this is because you cannot have more than 5 instances of the same batch class executing in your org.

 

To help you understand, try uploading 6 batches of data using apex data loader in a single operation. This will cause the apex trigger to run 6 times which will eventually try to create 6 instance of the batch apex class. You will see that the last batch execution will not happen and the 6th load batch will error out.

 

Because of the large volume of data that you want to integrate, I think that the option we have is to have a daily sync of the account data. You can have your apex trigger to mark the records that need to be synced with the external system. Consider using a flag checkbox that will be set to true by the trigger. Now you can use a schedulable apex class to call a batch apex which will query for the flagged accounts and then use callouts to sync that data with the external system.

 

I think with this approach, you can cut down on the number of callouts made, as even though there are 10000 updates you wouldnt be communicating every update to the external system, rather only the last update on that day. But as you may now realize, the time lag might be an issue. To improve the time lag, you could reduce the schedule to have the batch apex job fired on an hourly basis is you want. But batch apex itself has some limitations. Read through the governor limits documentation to see how exactly they might impact your case.

 

One last note: Any apex transaction cannot make more than 10 callouts, so your batch apex will need to be 'scoped' to make sure that the execute method does not make more than 10 callouts in a transaction.

This was selected as the best answer
Anto HotelbedsAnto Hotelbeds

Thansk Jerun.

 

I will finally go for your solution. In the trigger I will mark the accounts that need to be synch and then I will create an schedulable class that will call my apex batch. But I will try to schedule it to run every 10 minutes.

 

How to do it? I will ask this question a new thread of the phorum.

 

Really helpfull your advices.  Really appreciate it. Thanks,

 

Antonio

Prak088Prak088

@

Anto HotelbedsAnto Hotelbeds

Hi,

 

This is my test class:

 

@isTest
private class AccountSynchBatchTest {

    static testMethod void myUnitTest() {
    	       
    	
        AccountSynchBatch bc = new AccountSynchBatch();
        Database.executeBatch(bc, 1); 

    }
}

 

Prak088Prak088

@