+ Start a Discussion
dmchengdmcheng 

How does Iterable work in Batch Apex?

I've read and re-read the Iterable section in the Apex reference manual but I just don't get it.  How do you specify the SOQL query to select the records for processing?  Or do you just pass a List of objects into Database.executeBatch(), and does that List become the scope in the example on page 253?

And finally - if Iterable is subject to the standard governor limit on SOQL retrieved records, why would you use it?  I can't see the benefit of using it vs. a standard Apex class if the limits are the same.

Thanks
David

 

Best Answer chosen by Admin (Salesforce Developers) 
sfdcfoxsfdcfox

My example only showed a custom Iterator class, not the Batch Apex Class that would leverage such a class. You are correct in assuming that you would need two classes: your custom Iteration class plus a batch class that uses that class.

 

The idea here is that the Force.com platform basically does this:

 

 

public class Database {
  public static void executeBatch(Database.Batchable batch) {
    executeBatch(batch, 200);
  }
  public static void executeBatch(Database.Batchable batch, Integer limit) {
    Database.BatchableContext BC = new Database.BatchableContext();
    Database.Iterator iterator = batch.start(BC);
    List<Object> items = new List<Object>();
    while(iterator.hasNext()) {
      items.add(iterator.next());
      if(items.size()==limit) {
        batch.execute(BC,items);
        items.clear();
      }
    }
    batch.finish(BC);
  }
}

Of course, the logic that the Force.com platform uses has greater complexity, including resetting governor limits, etc, but this is the basic design of the Batchable/Iterator interface. As long as your iterator returns the correct values for hasNext and next, it can iterate through any sort of set of data you could imagine, including mathematical sequences, extended search results from a service such as Twitter, and other large data sources.

 

All Answers

NaishadhNaishadh

You can pass query as parameter value on class constructor.

 

One benifit compare to standard class vs. apex for iterator

"Each execution of a batch Apex job is considered a discrete transaction. For example, a batch Apex job that contains 1,000 records and is executed without the optional of 200 records each. The Apex governor limits are reset for each transaction. If the fails, the database updates made in the scope parameter from Database.executeBatch is considered five transactionsfirst transaction succeeds but the secondfirst transaction are not rolled back."

 

sfdcfoxsfdcfox

The iterator examples specify that you can come up with your own data structure as opposed to being limited to just a standard query. This means that you could do some really interesting things.

 

For example, the custom iterator could return a list of dynamically created records not based on any sort of query:

 

 

global class AccountGenerator implements Iterator<Account> {
  Integer Counter;
  public AccountGenerator(Integer GeneratorLimit) {
    Counter = GeneratorLimit;
  }
  global boolean hasNext() {
    return Counter > 0;
  }
  global Account next() {
    return new Account(Name='Test '+Counter--);
  }
}

While the example above is still practically useless, imagine using code like this to dynamically refill a custom object of 25,000 license keys for a software application you're selling on a timed schedule.

 

You could consume really large web service loops, perhaps for example if you were querying another organization via the API and wanted to use query/queryMore. Of course, I'm sure that this falls into the tricky limit of only one callout method per invocation, but the documentation does seem to suggest that it should work.

 

 

Finally, consider the fact that one could also use a custom iterator to provide preprocessing on the data as it moves through into the Batch Apex Class. While it may have the same limits, it has a certain flexibility that you won't find when you limit yourself to just SOQL statements. Database.getQueryLocator is great if the data you're processing is already in Salesforce, and so most developers will never need a custom iterator. But if you're an exception, and you have a special need to generate the data from another source or on the fly, this is when you would use the feature.

dmchengdmcheng

@sfdcfox - thanks for your reply.  I'm confused  though - the reference guide example has start, execute and finish methods, and your code does not.  Is your sample code supposed to precede these methods?

 

 

 

global class batchClass implements Database.batchable{
	global Iterable start(Database.BatchableContext info){
		return new CustomAccountIterable();
	}
	global void execute(Database.BatchableContext info, List<Account> scope){
		List<Account> accsToUpdate = new List<Account>();
		for(Account a : scope){
			a.name = 'true';
			a.numberOfEmployees = 70;
			accsToUpdate.add(a);
		}
	update accsToUpdate;
	}

	global void finish(Database.BatchableContext info){
	}
}

 

 

sfdcfoxsfdcfox

My example only showed a custom Iterator class, not the Batch Apex Class that would leverage such a class. You are correct in assuming that you would need two classes: your custom Iteration class plus a batch class that uses that class.

 

The idea here is that the Force.com platform basically does this:

 

 

public class Database {
  public static void executeBatch(Database.Batchable batch) {
    executeBatch(batch, 200);
  }
  public static void executeBatch(Database.Batchable batch, Integer limit) {
    Database.BatchableContext BC = new Database.BatchableContext();
    Database.Iterator iterator = batch.start(BC);
    List<Object> items = new List<Object>();
    while(iterator.hasNext()) {
      items.add(iterator.next());
      if(items.size()==limit) {
        batch.execute(BC,items);
        items.clear();
      }
    }
    batch.finish(BC);
  }
}

Of course, the logic that the Force.com platform uses has greater complexity, including resetting governor limits, etc, but this is the basic design of the Batchable/Iterator interface. As long as your iterator returns the correct values for hasNext and next, it can iterate through any sort of set of data you could imagine, including mathematical sequences, extended search results from a service such as Twitter, and other large data sources.

 

This was selected as the best answer
nagalakshminagalakshmi

Hi Sfdcfox,

 

I have displayed the list of names in select options. And i have place select, unselect and update buttons. If i move the name from left to right select option click on update button it will insert the records in to child object. 

 

For this i have collect the values in to list. My list having morethan 10000 records. When i am inserting the records i am getting error as 'Too Mant DML rows'. For this i have wrote the batch apex class. I am faced the same error when i used the batch apex also. How can i solve this issue. And how can i use the iterable class for this. How to pass my list values in to batch apex class. Please help me out. 

 

My batch apex class is

 

global class batchinsert implements Database.Batchable<sObject>
{
Manageproductcategories m=new Manageproductcategories();
global final String Query;
global batchinsert()
{

}

global Database.QueryLocator start(Database.BatchableContext BC)
{
//return null;
return Database.getQueryLocator(query);
}

global void execute(Database.BatchableContext BC, List<storeproduct_category__C> scope)
{

/*
List <storeproduct_category__c> lstAccount = new list<storeproduct_category__C>();
for(sobject s : scope)
{
//lstAccount(s);
}*/

insert scope;



}
global void finish(Database.BatchableContext BC)
{
}

}
I have executed this from my class like as
Database.BatchableContext BC;
batchinsert b=new batchinsert();
b.execute(BC,updatestorebrand);
updatestorebrand is list name. And i have passed my list values to execute method. Please help me out.
Thanks
 
tggagnetggagne

I'm struggling with the iterator/iterable thing myself.

 

My batch job needs to walk through 6.5 million records, group them by an account number, then hand off the groups of account numbers to be processed individually.

 

What I'm struggling with is how can I query 6.5 million rows, collect records with common account numbers, then return that collection of records as the iterable response.

 

I have more reading to do, but any direction is appreciated.

tggagnetggagne

Never mind.  I don't know if mine is the perfect implementation (still testing) but at least it compiles.

geetha charangeetha charan
This could be the easiest way i gues

global class ExampleBatchClass1 implements Database.Batchable<sObject>,Database.Stateful{

     List<SObject> finaltf = new List<SObject>();
     global ExampleBatchClass1(List<SObject> tfvalues){
                finaltf = tfvalues;             
     }
    
     // Start Method
     global List<SObject> start(Database.BatchableContext BC){
      return finaltf;
     }
global void execute(Database.BatchableContext BC, List<SObject> scope){
update or delete or insert according to your need
}

}
Gaya3Gaya3
Hi Geetha,

I tried this but its throwing error
geetha charangeetha charan
Hi Gaya3 

What error you are getting exactly? 
Can you post the error message.
I am using the same format and it is working fine for me. 
Harprit DhanoaHarprit Dhanoa
Hi Geetha,
It works like a charm.
I extended it's functionality and made it generic

 
// This is a generic class which can be used to run 5 DML operations in SF for all sObjects
// The constructor needs to be passed the following 4 parameters
//      1)  sobjListPassed 	 - List of objects that will have the dml operations executed upon.
//	2)  dmlOperationPassed 	 - 'Insert','Update','Delete','Upsert', 'Undelete'
//	3)  statusSObjectIdPassed  - The Id of the Object that will get it's Status field updated
//	4)  statusSObjectFieldNamePassed - The status field that will reflect the status of the dml operation
// 
// Harry Dhanoa 8/July/2015

global class GenericBatchDMLOperation implements Database.Batchable<sObject>,Database.Stateful {
    
    public Set<String> possibleDmlOperation = new Set<String>{'Insert','Update','Delete','Upsert','Undelete'};
    public String dmlOperation;
    public List<SObject> sobjList = new List<SObject>();
    public SObject statusSObject;
    public String statusSObjectFieldName;
    public AsyncApexJob aAJ;
    public Boolean updateQuote;
    
    global GenericBatchDMLOperation(List<SObject> sobjListPassed, 
                                                             String dmlOperationPassed,
                                                             Id statusSObjectIdPassed, 
                                                             String statusSObjectFieldNamePassed){
		Schema.SObjectType sobjectType = statusSObjectIdPassed.getSObjectType();
		String sobjectName = sobjectType.getDescribe().getName();
		statusSObject = Database.query('Select '+ statusSObjectFieldNamePassed +' From ' + sobjectName + ' Where Id = :statusSObjectIdPassed');
		statusSObjectFieldName = statusSObjectFieldNamePassed;
		 
        sobjList = sobjListPassed;  
        if(possibleDmlOperation.contains(dmlOperationPassed)) 
        	dmlOperation = dmlOperationPassed;        	                            
    }

    global List<SObject> start(Database.BatchableContext BC) {
    	aAJ = [ SELECT Id, Status, NumberOfErrors, JobItemsProcessed,TotalJobItems, CreatedBy.Email
                FROM AsyncApexJob WHERE Id = :BC.getJobId()];
        if(aAJ != null && statusSObject!=null && statusSObjectFieldName!=null) {
        	statusSObject.put(statusSObjectFieldName,aAJ.Status);
            update statusSObject;
        } 
        return sobjList;    
    }
   
    global void execute(Database.BatchableContext BC, List<SObject> scope) {
        if(dmlOperation == 'Insert')
            Insert scope;
        else if(dmlOperation == 'Update')
            Update scope;    
        else if(dmlOperation == 'Delete')
            Delete scope;    
        else if(dmlOperation == 'Upsert')
            Upsert scope;    
        else if(dmlOperation == 'Undelete')
            Undelete scope;                
    }   
    
    global void finish(Database.BatchableContext BC) {
    	aAJ = [ SELECT Id, Status, NumberOfErrors, JobItemsProcessed,TotalJobItems, CreatedBy.Email
                FROM AsyncApexJob WHERE Id = :BC.getJobId()];
    	if(aAJ != null && statusSObject!=null && statusSObjectFieldName!=null) {
        	statusSObject.put(statusSObjectFieldName,aAJ.Status);
            update statusSObject;
        }          
    }
}