BATCH-1056:
common-patterns.xml: *Replaced reference to RepeatOperationsStepFactoryBean with a valid configuration. *Rewrote "Adding a Footer Record" to discuss FlatFileFooterCallback job.xml: *Fixed error in the way JobParameters are defined for the CommandLineJobLauncher.
This commit is contained in:
@@ -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 <classname>Step</classname> through the
|
||||
<classname>RepeatOperationsStepFactoryBean</classname>:</para>
|
||||
<classname>SimpleStepFactoryBean</classname>:</para>
|
||||
|
||||
<programlisting><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></programlisting>
|
||||
|
||||
@@ -156,11 +152,9 @@
|
||||
}
|
||||
|
||||
public void afterRead(Object item) {
|
||||
|
||||
if (isPoisonPill(item)) {
|
||||
stepExecution.setTerminateOnly(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}</programlisting>
|
||||
@@ -175,59 +169,125 @@
|
||||
<section>
|
||||
<title>Adding a Footer Record</title>
|
||||
|
||||
<para>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
|
||||
<classname>ItemWriter</classname>. In this case, since a job is
|
||||
accumulating state that should not be lost if the job aborts, the
|
||||
<classname>ItemStream</classname> interface should be implemented:</para>
|
||||
<para>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 <classname>FlatFileFooterCallback</classname>
|
||||
interface provided by Spring Batch. The
|
||||
<classname>FlatFileFooterCallback</classname> (and its counterpart, the
|
||||
<classname>FlatFileHeaderCallback</classname>) are optional properties of
|
||||
the <classname>FlatFileItemWriter</classname>:</para>
|
||||
|
||||
<programlisting>public class CustomItemWriter implements ItemWriter<Trade>,
|
||||
ItemStream, StepListener
|
||||
{
|
||||
<programlisting> <bean id="itemWriter"
|
||||
class="org.springframework.batch.item.file.FlatFileItemWriter">
|
||||
<property name="resource" ref="outputResource" />
|
||||
<property name="lineAggregator" ref="lineAggregator"/>
|
||||
<emphasis role="bold"><property name="headerCallback" ref="headerCallback" /></emphasis>
|
||||
<emphasis role="bold"><property name="footerCallback" ref="footerCallback" /></emphasis>
|
||||
</bean>
|
||||
</programlisting>
|
||||
|
||||
private static final String TOTAL_AMOUNT_KEY = "total.amount";
|
||||
<para>The footer callback interface is very simple. It has just one method
|
||||
that is called when the footer must be written:</para>
|
||||
|
||||
private ItemWriter delegate;
|
||||
<programlisting> public interface FlatFileFooterCallback {
|
||||
|
||||
void writeFooter(Writer writer) throws IOException;
|
||||
|
||||
}</programlisting>
|
||||
|
||||
<section>
|
||||
<title>Writing a Summary Footer</title>
|
||||
|
||||
<para>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.</para>
|
||||
|
||||
<para>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:</para>
|
||||
|
||||
<programlisting> 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);
|
||||
}
|
||||
|
||||
}</programlisting>
|
||||
public void setDelegate(ItemWriter delegate) {...}
|
||||
}</programlisting>
|
||||
|
||||
<para>The custom writer in the example is stateful (it maintains its total
|
||||
in an instance variable <varname>totalAmount</varname>), but the state is
|
||||
stored through the <classname>ItemStream</classname> interface in the
|
||||
<classname>ExecutionContext</classname>. In this way we can be sure that
|
||||
when the <code>open()</code> 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
|
||||
<classname>ItemStream</classname>. For example, if the
|
||||
<classname>ItemWriter</classname> 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.</para>
|
||||
<para>This <classname>TradeItemWriter</classname> stores a
|
||||
<code>totalAmount</code> value that is increased with the
|
||||
<code>amount</code> from each <classname>Trade</classname> item written.
|
||||
After the last <classname>Trade</classname> is processed, the framework
|
||||
will call <methodname>writeFooter</methodname>, which will put that
|
||||
<code>totalAmount</code> into the file. In order for the method to be
|
||||
called, the <classname>TradeItemWriter</classname> must be wired into
|
||||
the <classname>FlatFileItemWriter</classname> as the
|
||||
<code>footerCallback</code>:</para>
|
||||
|
||||
<programlisting> <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"/>
|
||||
<emphasis role="bold"><property name="footerCallback" ref="tradeItemWriter" /></emphasis>
|
||||
</bean>
|
||||
</programlisting>
|
||||
|
||||
<para>The way that the <classname>TradeItemWriter</classname> has been
|
||||
so far will only function correctly if the <classname>Step</classname>
|
||||
is not restartable. This is because the class is stateful (since it
|
||||
stores the <code>totalAmount</code>), but the <code>totalAmount</code>
|
||||
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
|
||||
<classname>ItemStream</classname> interface should be implemented along
|
||||
with the methods <methodname>open</methodname> and
|
||||
<methodname>update</methodname>: </para>
|
||||
|
||||
<programlisting> 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);
|
||||
}</programlisting>
|
||||
|
||||
<para>The <methodname>update</methodname> method will store the most
|
||||
current version of <code>totalAmount</code> to the
|
||||
<classname>ExecutionContext</classname> just before that object is
|
||||
persisted to the database. The <methodname>open</methodname> method will
|
||||
retrieve any existing <code>totalAmount</code> from the
|
||||
<classname>ExecutionContext</classname> and use it as the starting point
|
||||
for processing, allowing the <classname>TradeItemWriter</classname> to
|
||||
pick up on restart where it left off the previous time the
|
||||
<classname>Step</classname> was executed.</para>
|
||||
|
||||
<para>It should be noted that it is not always necessary to implement
|
||||
<classname>ItemStream</classname>. For example, if the
|
||||
<classname>ItemWriter</classname> 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.</para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
||||
@@ -494,9 +494,9 @@
|
||||
linkend="domain" />. The first argument is 'endOfDayJob.xml', which is
|
||||
the Spring <classname>ApplicationContext</classname> containing the
|
||||
<classname>Job</classname>. The second argument, 'endOfDay' represents
|
||||
the job name. The final argument, 'schedule.date=01-01-2008' will be
|
||||
converted into <classname>JobParameters</classname>. An example of the
|
||||
XML configuration is below:</para>
|
||||
the job name. The final argument, 'schedule.date(date)=2008/01/01'
|
||||
will be converted into <classname>JobParameters</classname>. An
|
||||
example of the XML configuration is below:</para>
|
||||
|
||||
<programlisting> <job id="endOfDay">
|
||||
<steps>
|
||||
|
||||
Reference in New Issue
Block a user