2

I've been working on a chainable batch class. The constructor can take in either a service & query or a service & list of records. In almost all cases, the service/query are passed in. In these cases, I'm expecting the query to return a large number of records. As such, I need to use QueryLocator. There is currently only one situation in which I need to pass in a service/list, so I'd prefer to not have to make a whole new class just to handle it.

public class ChainableBatchManager implements Database.Batchable<SObject>, Database.Stateful, Database.AllowsCallouts {
    private final Integer batchesOf20 = 20;
    private ChainableBatch[] batchServices = new ChainableBatch[]{};

    public ChainableBatchManager() {}

    private ChainableBatchManager(ChainableBatchManager previousBatchManager) {
        this.batchServices = previousBatchManager.batchServices;
    }

    public ChainableBatchManager add(IProcessRecords recordService, String query) {
        ChainableBatch batchToAdd = new ChainableBatch(recordService, query);
        this.batchServices.add(batchToAdd);
        return this;
    }

    public ChainableBatchManager add(IProcessRecords recordService, SObject[] records) {
        ChainableBatch batchToAdd = new ChainableBatch(recordService, records);
        this.batchServices.add(batchToAdd);
        return this;
    }

    public Iterable<SObject> start(Database.BatchableContext context) {
        return batchServices[0].getRecords();
    }

    public void execute(Database.BatchableContext context, SObject[] scope) {
        try {
            batchServices[0].processRecords(scope);
        } catch(Exception ex) {
            // Error handling here
        }
    }

    public void finish(Database.BatchableContext context) {
        batchServices.remove(0);
        if (this.batchServices.isEmpty()) return;
        Database.executeBatch(new ChainableBatchManager(this));
    }

    public void executeBatch() {
        if (batchServices.isEmpty()) return;

        Database.executeBatch(this, batchesOf20);
    }

    public class ChainableBatch {
        public String query;
        public Iterable<SObject> records;
        public IProcessRecords recordService;

        public ChainableBatch(IProcessRecords recordService, String query) {
            this.recordService = recordService;
            this.query = query;
        }

        public ChainableBatch(IProcessRecords recordService, SObject[] records) {
            this.recordService = recordService;
            this.records = records;
        }

        public Iterable<SObject> getRecords() {
            if (records == null) records = (Iterable<SObject>) Database.getQueryLocator(query).iterator();

            return records;
        }
    }
}

So, as you can see in the inner ChainableBatch class, I'm trying to cast the QueryLocatorIterator as an Iterable. This is because I assumed returning an Iterable would allow me to pass back either a predefined list of records, OR a QueryLocatorIterable.

Is there some way to extract a list from the QueryLocatorIterator? Or is there some other way that I can return some generic list/iterable, whether it be a QueryLocatorIterator or a List?

Edit: Forgot to state the issue I've been running into with the code as is. I've tested this class by running a test class that uses it indirectly. I'm getting this error when I run that test: System.SerializationException: Not Serializable: Database.QueryLocator And the Stack Trace just says "External entry point"

1
  • This is an old post, but you might want to check if this answer can help
    – Shamina
    Jul 2, 2021 at 6:50

0

Your Answer

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

Browse other questions tagged or ask your own question.