Repeat a simple operation such as processing a data item, or a message, up to a fixed number of times, normally with a transaction scoped to the whole batch. Transaction resources are shared between the operations in the batch, leading to performance benefits.
The operation to be repeated:
Client code can locate and acquire all the resources it needs for the batched operation, and can force transactions to rollback for testing purposes.
We are often interested in a specific scenario of this use case where the batched operation is:
The vanilla successful batch use case proceeds as follows:
If one of the operations rolls back it will throw an exception. Normal transaction semantics determine what happens next. Usually (in the scenario described above) there is an outer transaction for the whole batch, which rolls back as well: all the messages remain unsent, and all the data remain uncommitted. A retry will receive exactly the same initial conditions.
The batch size is not fixed. The use case proceeds as above, but in the middle of a batch operation execution:
Instead of the Framework waiting for each operation to complete it could spin them off independently into separate threads or a work queue. The batch still has to have a definite endpoint, so the Framework waits for all the operations to finish or fail before cmpleting the batch.
Client may wish to inspect the state of the ongoing batch operation, and potentially force an early completion.
batchTemplate.iterate(new RepeatCallback() { public boolean doInIteration() { // do stuff } });
final ItemProvider provider = new JmsItemProvider(); final ItemProcessor processor = new ItemProcessor() { public void process(Object data) { // do something with the data (a record) } }; batchTemplate.execute(new RepeatCallback() { public boolean doInIteration() { Object data = provider.next(); if (data!=null) { processor.process(data); } return data!=null; } });
batchTemplate.iterate(new RepeatCallback() { public boolean doInIteration() { // do stuff } });
where the batch template might itself use a TaskExecutor internally, or
batchTemplate.iterate(new Runnable() { public void run() { // do stuff with data }; });
where the batch template is a TaskExecutor. Probably the former because it is more encapsulated: it gives the framework more freedom to implement the template in any way it needs to, e.g. to accommodate more complicated use cases.
public class RepeatTemplate implements RepeatOperations { public void iterate(RepeatCallback callback) { // set up the batch interceptors.open(); while (running) { // allow interceptor to pre-process and veto continuation interceptor.before(); // continue only if batch is ongoing if (running = callback.doInIteration()!=null) { interceptor.after(); } } // clean up or commit the whole batch interceptor.close(); } }
The RepeatInterceptor can be stateful, and can store up inserts until the end of the batch. If the RepeatTemplate.iterate is transactional then they will only happen if the transaction is successful.
This way the client can even decide to use a batch interceptor that runs in its own transaction at the end of the batch.
public class RepeatTemplate implements RepeatOperations { public void iterate(RepeatCallback callback) { // set up the batch session RepeatContext context = completionPolicy.start(); while (!completionPolicy.isComplete(context)) { // callback gets the context as an argument callback.doInIteration(context); completionPolicy.update(context); } } }
public Object doMyBatch() { // do some processing // something bad happened... RepeatContext context = RepeatSynchronizationManager.getContext(); context.setCompleteOnly(); }