Build large single XML Document over multiple batches

apexbatchgovernorlimitsxml

I would like to understand if it's possible to generate large XML files using XmlNode and Document classes in Batch apex?

Ideally, I would like the Dom.Document class variable to be stateful. However, this is not possible. As an attempted workaround, I have serialised the document into a string. This works in execute anonymous however it does not work in batch (System.SerializationException: Not Serializable: dom.XmlNode).

I have tried creating the file synchronously and also using @Future however both failed with CPU timeout beyond a small number of records. That is why I would like to create the XML document in small chunks within Salesforce governor limits.

Note that the code below has been simplified for illustration purposes.

Thanks in advance

public with sharing class ExampleBatchJob  implements Database.Batchable<sObject>, Database.Stateful {

Dom.Document doc = new DOM.Document();
Dom.XmlNode root = doc.createRootElement('AccountPerformanceSalesData', null, null);
String theDocAsString = doc.toXmlString();

public Database.QueryLocator start(Database.BatchableContext BC) {
    String query = 'SELECT Id FROM Account';
    return Database.getQueryLocator(query);
}

public void execute(Database.BatchableContext BC, List<sObject> scope) {

    Dom.Document doc2 = new DOM.Document();
    doc2.load(theDocAsString);
    List<Account> accountList = scope;
    for (Account acc : accountList) {
        doc2.getRootElement().addChildElement('test', 'test', 'test');
    }
    theDocAsString = doc2.toXmlString();
}

public void finish(Database.BatchableContext BC) {
    System.debug(theDocAsString);
}

Best Answer

You can't serialize doc or root. Remove them from the class, and initialize theDocAsString in your start(Database.BatchableContext context) method.

public Database.QueryLocator start(Database.BatchableContext BC) {
  Dom.Document doc = new Dom.Document();
  doc.createRootElement(...);
  theDocAsString = doc.toXmlString();
  return Database.getQueryLocator([SELECT Id FROM Account]);
}