You need to sign in to do that
Don't have an account?
Scheduling Apex Batch with Callouts-Future method cannot be called/uncommited work/too many callouts
I have a class which does HTTP callouts. Because I am needing to do more than 10 callouts at a time, I am using a Batch class to call that class. At this point, I can call that class without issue by directly calling the batch execute via system log. The next step I need to do is put the Batch in a scheduler that runs every hour. So I now have 4 classes:
1.) My original class that does the HTTP callouts
2.) The batch class which calls #1 scoped one record at a time to avoid the 10-callout limit
3.) A schedulable class which executes the batch class and has a method to invoke system.scheduler
But now I'm stuck because it seems no matter how I organize things, I get one of these errors:
1.) Future method cannot be called from a future method (I'm getting this one with my current code setup)
2.) You have uncommitted work pending (I get this one if the callout method isn't a future method)
3.) Too many callouts - 2 (I get this one if I make the callout method NOT a future method and somehow try to update the assets all together in the batch class)
What the heck am I supposed to do to get this class scheduled?? Below is my code.
Class #1: The HTTP Callout Class
global class VLStatus {
@future(callout=true)
global static void getMachineStatus(String asstId){
Asset asst = new Asset();
asst = [select id, name, SerialNumber from Asset where Id = :asstId];
//code that does my callout stuff and sets the asset fields
update asset;
}
Class #2: The Batch Class
global class VLStatusBatch implements Database.Batchable<Asset>, Database.AllowsCallouts{
//Get list of machines that we are still waiting on for a response
Asset[] AssetsToUpdate = [Select id, name, SerialNumber from Asset where Pending_VL_Response__c = 1];
global Iterable<Asset> start(database.batchablecontext BC){
return (AssetsToUpdate);
}
global void execute(Database.BatchableContext BC, List<Asset> scope){
for(Asset a : scope){
VLStatus.getMachineStatus(a.Id);
}
}
global void finish(Database.BatchableContext info){
}//global void finish loop
}
Class #3: The Schedulable Class
global class HourlyScheduler implements Schedulable{
global HourlyScheduler (){}
public static void start(){
//Seconds Minutes Hours Day_of_month Month Day_of_week optional_year
System.schedule('Pending Machine Status Update', '0 5 1-23 * * ?', New HourlyScheduler());
}
global void execute(SchedulableContext ctx){
VLStatusBatch b = new VLStatusBatch();
database.executebatch(b);
}
}
I actually found my problem. I was so close.
First, you can't call a future method from a Batch class. My guess is that the threads generated from batches are considered "future" methods, so trying to do it results in the "Future method cannot be called from a future method" error. So, I removed the future designation from the HTTP callout class.
But what got it for me was making sure to pass the optional scope parameter when my schedulable class calls the batch. Otherwise I hit the wall for callout limits.
So this:
database.executebatch(b);
Becomes:
database.executebatch(b, 1);
And now the scheduled job runs perfectly, just as if I had manually run it myself.
All Answers
I actually found my problem. I was so close.
First, you can't call a future method from a Batch class. My guess is that the threads generated from batches are considered "future" methods, so trying to do it results in the "Future method cannot be called from a future method" error. So, I removed the future designation from the HTTP callout class.
But what got it for me was making sure to pass the optional scope parameter when my schedulable class calls the batch. Otherwise I hit the wall for callout limits.
So this:
database.executebatch(b);
Becomes:
database.executebatch(b, 1);
And now the scheduled job runs perfectly, just as if I had manually run it myself.
Thank you BBeaird, this is an excellent solution to what I'm doing. It's 10x better than the SFDC documentation
Seeing as it's timely, I thought I would add an update to this. In summer '12, Salesforce is increasing the callout limit in a batch to 10. This means I can bump my batch size from 1 to 10, which is nice. It also means I can drop an e-mail service I was using in another class as a way to "chain" batch jobs where I needed to do a couple callouts for one record.
One other caviat here is that I had to update the Execute method to add each asset to a list and then do an update on the list at the end of the loop rather than updating each asset individually. If you don't change that, you'll get the "uncommitted work" error. Plus, it's just good to keep DML actions out of loops wherever possible.
@BBeaird: I have a similar requirement.I am done with writing the batch class,class for call out and scheduler class.Now i need to schedule Batch for every one hour.I see that you have written VLStatusScheduler class with CRON expression for the executing scheduler class.
My question is after writing this class does it schedule the Batch automatically or do we have to do something else in order to schedule it.(I mean scheduling apex class or running script in developer console)???
@Prak088: I have since combined my scheduler and executions classes into one and updated my code above.
You are right - the final step is to actually start the scheduler so it will run once an hour. The way to do this is to open up an execute anonymous window through either eclipse or the developer console and run this:
hourlyScheduler.start();
Hi could you post the code for the combined class as well as the test class for it
The new combined class is below. To be honest, it's somewhat watered down from my actual code so it can be clearer, so trying to post the test class won't do you much good unless I modify it to be readable by external eyes. I don't have time for that the moment, but I'll see what I can do over the next week or so.
Class #3: The Schedulable Class
global class HourlyScheduler implements Schedulable{
global HourlyScheduler (){}
public static void start(){
//Seconds Minutes Hours Day_of_month Month Day_of_week optional_year
System.schedule('Pending Machine Status Update', '0 5 1-23 * * ?', New HourlyScheduler());
}
global void execute(SchedulableContext ctx){
VLStatusBatch b = new VLStatusBatch();
database.executebatch(b);
}
}
@BBeaird: Thanks a lot mate !! That was really helpful.
I guess writing test class for this would not be that diffcult and i would manage that.
Kudos!!!!!