1

I am writing a batch class which should be scheduled to run at a certain frequency. I am putting both interfaces on same class -

global with sharing class testbatch implements Database.Batchable<sObject>,Database.Stateful,Schedulable{

  Integer batchChunkSize;

  global testBatch(){
    //get chunksize value from a custom setting and assign it to variable batchChunkSize.
    batchChunkSize = (MyCustom_Setting__c.getOrgDefaults().chunkSize__c).intValue();
  }

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

  global void execute(Database.BatchableContext BC, List<Application__c> scope){
    //do stuff
  }

  global void finish(Database.BatchableContext BC){}

  global void execute(SchedulebleContext SC){
    testBatch currentInstanceOfBatch = new testBatch();
    Database.executeBatch(currentInstanceOfBatch,currentInstanceOfBatch.batchChunkSize);
  }
}

In the very last execute statement, I am confused as to if I should use the second parameter as just batchChunkSize or currentInstaceOfBatch.batchChunkSize.

Both of them are working. I don’t know how, but I want to understand the basics.

Let’s say I schedule this class to run daily. When it's time to run, the last execute statement is run (as far as I understand). At this stage, has an instance of class testBatch already been created? If so, then has the constructor already run (hence, batchChunkSize variable now has the value I want it to be)?

If an instance of class testBatch is already created by the time the last execute statement is run, then within that execute statement, do I need to do

testBatch currentInstanceOfBatch = new testBatch();

or can I do

testBatch currentInstanceOfBatch = this;

This is the first time I am doing both interfaces together, so I am a little confused as to what gets instantiated when.

------ Updating the questions in my head after reading the excellent response from David Reed ----

Please go through things I am describing and point out where I am wrong.

So let's say, this job is scheduled to run everyday at 8:00 AM. When people say "Scheduler instance", what does it mean? When Monday 8:00 AM comes in, will an instance of testBatch be created? Is that what is meant by Scheduler Instance? If yes, let's call it instance1. So instance1 runs at Monday 8:00 AM. execute (SchedulableContext) of Instance1 is called. Now within the execute statement, I create a new instance of this class (call it Instance2), pass it as a parameter to Databse.executeBatch which will kick start the batch job for the day.

When Instance1.execute method is run, in the statement Database.executeBatch, the second parameter can be Instance1 member variable batchChunkSize or Instance2 member variable batchChunkSize. Both should be the same, right?

Now the next time a job is scheduled to run is Tuesday 8:00 AM. This whole process will start again. A new instance of class will be generated on Tuesday 8:00 AM and its execute (SchedulableContext) method will get called. Am I correct? If so, any change in custom setting value of batch size between Monday and Tuesday should reflect on the Tuesday instance.

I know I am all sorts of wrong in assuming when instances are being created. I would appreciate a patient explanation of the concepts. In database.executeBatch, if I pass a second parameter.

----- Final Update ------

The answers provided by David and sfdcfox are excellent. Anyone stumbling on this post, I recommend reading both the answers.

2 Answers 2

6

This method:

  global void execute(SchedulableContext SC){
    testBatch currentInstanceOfBatch = new testBatch();
    Database.executeBatch(currentInstanceOfBatch,currentInstanceOfBatch.batchChunkSize);
  }

executes each time the Schedulable job is executed. At that time, a new instance of testBatch is created, executing its constructor:

 global testBatch(){
    //get chunksize value from a custom setting and assign it to variable batchChunkSize.
    batchChunkSize = (MyCustom_Setting__c.getOrgDefaults().chunkSize__c).intValue();
  }

which obtains the value of your Custom Setting at that time and assigns it to the new instance's batchChunkSize variable. It is then passed to Database.executeBatch():

Database.executeBatch(currentInstanceOfBatch,currentInstanceOfBatch.batchChunkSize);

The net outcome is that each time your batch class is enqueued by the scheduled job, the value of the Custom Setting at that time is used to set the batch size. Accessing the Custom Setting within the batch job's constructor is probably adding excess confusion; you could simply do it execute().

in the very last execute statement, i am confused as to i should put second parameter as just batchChunkSize or currentInstaceOfBatch.batchChunkSize

Because the scheduler class is the same class, it too has a member batchChunkSize. However, the value of its member variable was set at the time the scheduler instance was created and enqueued. If you use it here, your batch class will not adapt to changes in the Custom Setting until you unschedule and reschedule the scheduler.


I think that your life is generally going to be easier if you simplify the whole batch size issue by doing

  global void execute(SchedulebleContext SC){
    Database.executeBatch(
        new testBatch(),
        MyCustom_Setting__c.getOrgDefaults().chunkSize__c
    );
  }

Then you don't have to worry about the persistence of the member variable at all.

1
  • i am bit confused. Had too many follow up questions. So i updated the post. Appended some questions i had. Would you be kind enough to provide some insight into them please? I guess i am confused as to when Instances get created when we "schedule a class". Forget about batch class. If its a simple schedulable class, scheduled to run everyday, will a new instance be created everyday? From your comment it seems like "No, One instance is created when you click the schedule button". But i want to be sure. May 8, 2021 at 0:29
2

When you call any of the asynchronous methods, the memory graph of the object is serialized, and used for all future versions of that call (barring Database.Stateful, which allows the Batchable to modify its internal state, if implemented on the class). For Queueable and Batchable, this doesn't matter, as they only fire once, but Schedulable jobs can repeat.

The constructor is called when you created the class, not when it later subsequently execute()s. It has already been persisted to the database, and later deserialized at that point. This is the "scheduler instance" that people refer to. You can also have a "queueable instance" and a "batchable instance."

Consider this contrived example:

public class ScheduledCounter implements Schedulable {
  Integer counter = 0;
  public void execute(SchedulableContext context) {
    System.assertEquals(0, counter++);
    System.assertEquals(1, counter);
  }
}

This code will never fail its assertion, even though we are modifying counter every time execute() is called. This is because the 0 is reloaded every time the job runs. The counter has been frozen in place and will be deserialized each time the execute() method runs.

Given your example code, if you write:

 testbatch myBatch = new testbatch();
 System.schedule('MyJob', '0 0 0 * * ?', myBatch);

The "new" keyword is when your constructor is called, and the only time it is called. In fact, as a general rule of thumb, if you do not see a new keyword, no constructor will be called, and when you use a new keyword, a constructor will always be called (even if it is a zero-arg default constructor). This is true even for more trivial types of serialization, such as JSON.deserialize(jsonString, myClass.class). No new, no constructor.

This same logic also applies to Batchable and Queueable; the only moment a constructor is called is when you use the new keyword, and the moment you call Database.executeBatch() or System.enqueueJob(), a copy of the object is frozen in place at that moment.

Let's explore this a bit further. Let's have a class:

public class MyQueueable implements Queueable {
  public Integer myValue = 42;
  public void execute(QueueableContext context) {
    System.debug(myValue);
  }
}

Now, let's execute this:

MyQueueable job = new MyQueueable();
System.enqueueJob(job);
myJob.myValue = 5;

Hopefully, you'll expect to see an output of 42. Because the value was not changed until after System.enqueueJob(), it retained its original value. This also means that:

MyQueueable job = new MyQueueable();
for(Integer i = 0; i < 5; i++) {
  job.myValue = i;
  System.enqueueJob(job);
}

Will have 5 jobs run, and the output for each will be 0, 1, 2, 3, 4, in order (at least, in theory; you will have 5 jobs, and they will output one of each value, but could theoretically end up out of order). This is despite calling new just once, and despite modifying the "same" object every time. Its state was frozen in place at the moment it was passed to System.enqueueJob().

Hopefully, this answer explains the mechanisms in play in sufficient detail. Of course, David's answer is far more practical: if you want the scheduled job to change dynamically, you need to grab the Custom Setting/Custom Metadata at runtime, and not during construction.

4
  • Thank you so much for this explanation sfdcfox. This clarifies everything. Where is all this documented. I would love to read things like this and get my concepts clarified. I mean I understand the thing you said about constructors, but I had no information on when the class is serialized and “frozen”. Thank you so much. May 8, 2021 at 13:00
  • @user2957592 Does any such documentation exist? I'm not sure. This was all learnt experimentally by myself at some point. Like any "hacker," I'm always interesting in what makes things "tick," so this was knowledge I had to have. I think I really stumbled across this around the time I started experimenting with my Swiss Army Knife Batchable. This is actually one that's rather easy to explore experimentally.
    – sfdcfox
    May 8, 2021 at 13:24
  • This is great. i was actually looking for that. I remember reading one of your answer - salesforce.stackexchange.com/a/102985 and since then i have been wondering if you made a package out of it. Do you have a blog i can follow? I came across some blogs by you, but doesn't look like you were active there. For this post i have marked David's answer as the final answer, since he did provide accurate solution. Hope you dont mind that. May 8, 2021 at 15:50
  • @user2957592 I've been having medical issues... so it's been hard to do extra. I am hopefully going to get to it this year, though.
    – sfdcfox
    May 9, 2021 at 0:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.