diff --git a/docs/src/site/docbook/reference/common-patterns.xml b/docs/src/site/docbook/reference/common-patterns.xml index 36fd7304f..f2e61800f 100644 --- a/docs/src/site/docbook/reference/common-patterns.xml +++ b/docs/src/site/docbook/reference/common-patterns.xml @@ -124,17 +124,13 @@ strategy which signals a complete batch when the item to be processed is null. A more sophisticated completion policy could be implemented and injected into the Step through the - RepeatOperationsStepFactoryBean: + SimpleStepFactoryBean: <bean id="simpleStep" - class="org.springframework.batch.core.step.item.RepeatOperationsStepFactoryBean" > + class="org.springframework.batch.core.step.item.SimpleStepFactoryBean" > ... - <property name="chunkOperations"> - <bean class="org.springframework.batch.repeat.support.RepeatTemplate"> - <property name="completionPolicy"> - <bean class="org.example...SpecialCompletionPolicy"/> - </property> - </bean> + <property name="chunkCompletionPolicy"> + <bean class="org.example...SpecialCompletionPolicy"/> </property> </bean> @@ -156,11 +152,9 @@ } public void afterRead(Object item) { - if (isPoisonPill(item)) { stepExecution.setTerminateOnly(true); } - } } @@ -175,59 +169,125 @@
Adding a Footer Record - A very common requirement is to aggregate information during the - output process and to append a record at the end of a file summarizing the - data, or providing a checksum. This can also be achieved with a callback - in the step, normally as part of a custom - ItemWriter. In this case, since a job is - accumulating state that should not be lost if the job aborts, the - ItemStream interface should be implemented: + Often when writing to flat files, a "footer" record must be appended + to the end of the file, after all processing has be completed. This can + also be achieved using the FlatFileFooterCallback + interface provided by Spring Batch. The + FlatFileFooterCallback (and its counterpart, the + FlatFileHeaderCallback) are optional properties of + the FlatFileItemWriter: - public class CustomItemWriter implements ItemWriter<Trade>, - ItemStream, StepListener -{ + <bean id="itemWriter" + class="org.springframework.batch.item.file.FlatFileItemWriter"> + <property name="resource" ref="outputResource" /> + <property name="lineAggregator" ref="lineAggregator"/> + <property name="headerCallback" ref="headerCallback" /> + <property name="footerCallback" ref="footerCallback" /> + </bean> + - private static final String TOTAL_AMOUNT_KEY = "total.amount"; + The footer callback interface is very simple. It has just one method + that is called when the footer must be written: - private ItemWriter delegate; + public interface FlatFileFooterCallback { + void writeFooter(Writer writer) throws IOException; + + } + +
+ Writing a Summary Footer + + A very common requirement involving footer records is to aggregate + information during the output process and to append this information to + the end of the file. This footer serves as a summarization of the file + or provides a checksum. + + For example, if a batch job is writing Trade records to a flat + file, and there is a requirement that the total amount from all the + Trades is placed in a footer, then the following ItemWriter + implementation can be used: + + public class TradeItemWriter implements ItemWriter<Trade>, + FlatFileFooterCallback { + + private ItemWriter<Trade> delegate; private double totalAmount = 0.0; - public void setDelegate(ItemWriter delegate) { ... } - - public ExitStatus afterStep(StepExecution stepExecution) { - // Add the footer record here... - delegate.write("Total Amount Processed: " + totalAmount); - } - - public void open(ExecutionContext executionContext) { - if (executionContext.containsKey(TOTAL_AMOUNT_KEY) { - totalAmount = executionContext.getDouble(TOTAL_AMOUNT_KEY); + public void write(List<? extends Trade> items) { + delegate.write(items); + for(Trade trade : items) { + totalAmount += trade.getAmount(); } } - public void update(ExecutionContext executionContext) { - executionContext.setDouble(TOTAL_AMOUNT_KEY, totalAmount); - } - - public void write(Trade item) { - delegate.write(item); - totalAmount += item.getAmount(); + public void writeFooter(Writer writer) throws IOException { + writer.write("Total Amount Processed: " + totalAmount); } -} + public void setDelegate(ItemWriter delegate) {...} + } - The custom writer in the example is stateful (it maintains its total - in an instance variable totalAmount), but the state is - stored through the ItemStream interface in the - ExecutionContext. In this way we can be sure that - when the open() callback is received on a restart. The - framework garuntees we always get the last value that was committed. It - should be noted that it is not always necessary to implement - ItemStream. For example, if the - ItemWriter is re-runnable, in the sense that it - maintains its own state in a transactional resource like a database, there - is no need to maintain state within the writer itself. + This TradeItemWriter stores a + totalAmount value that is increased with the + amount from each Trade item written. + After the last Trade is processed, the framework + will call writeFooter, which will put that + totalAmount into the file. In order for the method to be + called, the TradeItemWriter must be wired into + the FlatFileItemWriter as the + footerCallback: + + <bean id="tradeItemWriter" class="..TradeItemWriter"> + <property name="delegate" ref="flatFileItemWriter" /> + </bean> + + <bean id="flatFileItemWriter" + class="org.springframework.batch.item.file.FlatFileItemWriter"> + <property name="resource" ref="outputResource" /> + <property name="lineAggregator" ref="lineAggregator"/> + <property name="footerCallback" ref="tradeItemWriter" /> + </bean> + + + The way that the TradeItemWriter has been + so far will only function correctly if the Step + is not restartable. This is because the class is stateful (since it + stores the totalAmount), but the totalAmount + is not persisted to the database, and therefore, it cannot be retrieved + in the event of a restart. In order to make this class restartable, the + ItemStream interface should be implemented along + with the methods open and + update: + + private static final String TOTAL_AMOUNT_KEY = "total.amount"; + + public void open(ExecutionContext executionContext) { + if (executionContext.containsKey(TOTAL_AMOUNT_KEY) { + totalAmount = executionContext.getDouble(TOTAL_AMOUNT_KEY); + } + } + + public void update(ExecutionContext executionContext) { + executionContext.setDouble(TOTAL_AMOUNT_KEY, totalAmount); + } + + The update method will store the most + current version of totalAmount to the + ExecutionContext just before that object is + persisted to the database. The open method will + retrieve any existing totalAmount from the + ExecutionContext and use it as the starting point + for processing, allowing the TradeItemWriter to + pick up on restart where it left off the previous time the + Step was executed. + + It should be noted that it is not always necessary to implement + ItemStream. For example, if the + ItemWriter is re-runnable, meaning that it + maintains its own state in a transactional resource like a database, + there is no need to maintain state within the writer itself. +
diff --git a/docs/src/site/docbook/reference/job.xml b/docs/src/site/docbook/reference/job.xml index 4c265f61d..e1fc41fcf 100644 --- a/docs/src/site/docbook/reference/job.xml +++ b/docs/src/site/docbook/reference/job.xml @@ -494,9 +494,9 @@ linkend="domain" />. The first argument is 'endOfDayJob.xml', which is the Spring ApplicationContext containing the Job. The second argument, 'endOfDay' represents - the job name. The final argument, 'schedule.date=01-01-2008' will be - converted into JobParameters. An example of the - XML configuration is below: + the job name. The final argument, 'schedule.date(date)=2008/01/01' + will be converted into JobParameters. An + example of the XML configuration is below: <job id="endOfDay"> <steps>