Earlier today I was working with some web services and needed to be able to update a number of accounts in batch. Well I didn’t so much NEED to, but I had to allow for that possibility. Due to the number of callouts being made to this external web service, any more than two accounts getting updated and I’d hit a callout limit. Part of me wants to scream #YourDoingItWrong, but that’s another story.
So I found myself pouring over the documentation, various blog entries, and forum postings to figure out how exactly to tackle the issue. I had already written the batch class to handle this, but it was once I tried putting that code into practice when things went awry. I had a sneaky suspicion — that turned out to be correct, but more on that shortly — that the finish method in an apex batch class would be something that just “happens” — it would just get called. However, in my testing it was appearing to me that I was wrong. To add to the frustration, I was unable to find evidence that the finish method happens automatically. (I was assuming it was akin to a try/catch/finally block with the finish method being analogous to the “finally” clause). However, it *had* to be that way and indeed it is, but I was still in the weeds. How about a little code? You can probably find where I went wrong. (Leaving out the juicy bits and replacing with pseudo code):
global class BatchPushAccount implements Database.Batchable, Database.AllowsCallouts { String query; List accountsPushed = new List(); List failures = new List(); global BatchPushAccount() { query = ' start(Database.BatchableContext BC) { return Database.getQueryLocator(query); } /* PERFORMED IN BATCHES OF ONE SINCE EXTERNAL SYSTEM HAS NUMEROUS * CALLS TO GET THINGS "DONE" */ global void execute(Database.BatchableContext BC, List scope) { for(Account a : scope) { ...call out to web service here ...if successful, set some values on account and then accountsPushed.add(a); ...else handle the failures behind the scenes failures.add(customresponse); } } global void finish(Database.BatchableContext BC) { if(accountsPushed.size() > 0) { System.debug('Running update on pushed accounts'); update accountsPushed; } if(failures.size() > 0) { System.debug('There were: ' + failures.size() + ' failures'); ServiceUtility.handleFailedResponses(failures); } } }
Upon running the above code I *NEVER* saw my debug statements from the finish method and sure enough, the records were not updated on the Salesforce side. This started making me wonder “maybe finish doesn’t just happen.” I did some more reading and realized that I was doing this dry run through anonymous apex and began to wonder if I actually had to invoke the finish method myself. I ran across another blog entry that didn’t come right out and say this, but it DID make me realize I had yet to write a Schedule class to do this and call the batch from apex code (hmmm). Which I promptly did, thusly assuming I’d call finish from there, (which is WRONG), but for the purposes of other newbies like me trying to see what that looks like, its pretty simple:
global class PushAccountSchedule implements Schedulable { global void execute(SchedulableContext sc) { BatchPushAccount b = new BatchPushAccount(); Database.executeBatch(b); } }
I couldn’t even begin to figure out *HOW* I’d call the finish method since it takes an instance of a BatchableContext…”batch.finish(batch)” sure as heck didn’t make any sense…but after reviewing my code once more — it was obvious. The finish method was INDEED running, but my debug statements were depending on one of two lists being populated (see the code near the top of the batch class). This is where the light-bulb went on. The finish method appears to happen in an entirely different request and therefore the lists that I thought I was populating during my “execute” phase were now in fact *empty*. This was easily proven by placing a debug statement just inside the finish method and trying again and upon so doing, my new debug statement appeared whilst neither of the other two showed up. The finish method was doing what it was supposed to, there was just nothing there for it to do anything with. In the end I moved my update code from the finish method into the execute method of my batch (outside of my for loop by the way — that being said, it wouldn’t have mattered really since these were running in batches of 1) which worked flawlessly. So now my code as an empty finish method since there’s nothing I need to do once the job completes.
Lesson Learned: the execute and finish methods in a batch class do not operate within the same context. File this under #GoodToKnow and #IMustHaveMissedThatSomewhere
:wq!
EDIT: I’ve found this now too…just don’t know if its worth it or if it matters at this point. #BetterPracticeSomewhere?
Please note: Any code I have in this post may have typos since I removed some juicy bits and renamed other pieces to protect the innocent…