Files
spring-batch/build/reference-epub-work/ch11s03.xhtml
Michael Minella 75ab909314 update
2017-03-23 10:18:33 -05:00

129 lines
7.4 KiB
HTML

<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xmlns:m="http://www.w3.org/1998/Math/MathML" xmlns:pls="http://www.w3.org/2005/01/pronunciation-lexicon" xmlns:ssml="http://www.w3.org/2001/10/synthesis" xmlns:svg="http://www.w3.org/2000/svg"><head><title>Adding a Footer Record</title><link rel="stylesheet" type="text/css" href="docbook-epub.css"/><meta name="generator" content="DocBook XSL Stylesheets V1.78.1"/><link rel="prev" href="ch11s02.xhtml" title="Stopping a Job Manually for Business Reasons"/><link rel="next" href="ch11s04.xhtml" title="Driving Query Based ItemReaders"/></head><body><header/><section class="section" title="Adding a Footer Record" epub:type="subchapter" id="addingAFooterRecord"><div class="titlepage"><div><div><h2 class="title" style="clear: both">Adding a Footer Record</h2></div></div></div>
<p>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 <code class="classname">FlatFileFooterCallback</code>
interface provided by Spring Batch. The
<code class="classname">FlatFileFooterCallback</code> (and its counterpart, the
<code class="classname">FlatFileHeaderCallback</code>) are optional properties of
the <code class="classname">FlatFileItemWriter</code>:</p>
<pre class="programlisting">&lt;bean id="itemWriter" class="org.spr...FlatFileItemWriter"&gt;
&lt;property name="resource" ref="outputResource" /&gt;
&lt;property name="lineAggregator" ref="lineAggregator"/&gt;
<span class="bold"><strong>&lt;property name="headerCallback" ref="headerCallback" /&gt;</strong></span>
<span class="bold"><strong>&lt;property name="footerCallback" ref="footerCallback" /&gt;</strong></span>
&lt;/bean&gt;</pre>
<p>The footer callback interface is very simple. It has just one method
that is called when the footer must be written:</p>
<pre class="programlisting">public interface FlatFileFooterCallback {
void writeFooter(Writer writer) throws IOException;
}</pre>
<section class="section" title="Writing a Summary Footer" epub:type="division" id="writingASummaryFooter"><div class="titlepage"><div><div><h3 class="title">Writing a Summary Footer</h3></div></div></div>
<p>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.</p>
<p>For example, if a batch job is writing
<code class="classname">Trade</code> records to a flat file, and there is a
requirement that the total amount from all the
<code class="classname">Trade</code>s is placed in a footer, then the following
<code class="classname">ItemWriter</code> implementation can be used:</p>
<pre class="programlisting">public class TradeItemWriter implements ItemWriter&lt;Trade&gt;,
FlatFileFooterCallback {
private ItemWriter&lt;Trade&gt; delegate;
private BigDecimal totalAmount = BigDecimal.ZERO;
public void write(List&lt;? extends Trade&gt; items) {
BigDecimal chunkTotal = BigDecimal.ZERO;
for (Trade trade : items) {
chunkTotal = chunkTotal.add(trade.getAmount());
}
delegate.write(items);
// After successfully writing all items
totalAmount = totalAmount.add(chunkTotal);
}
public void writeFooter(Writer writer) throws IOException {
writer.write("Total Amount Processed: " + totalAmount);
}
public void setDelegate(ItemWriter delegate) {...}
}</pre>
<p>This <code class="classname">TradeItemWriter</code> stores a
<code class="code">totalAmount</code> value that is increased with the
<code class="code">amount</code> from each <code class="classname">Trade</code> item written.
After the last <code class="classname">Trade</code> is processed, the framework
will call <code class="methodname">writeFooter</code>, which will put that
<code class="code">totalAmount</code> into the file. Note that the
<code class="methodname">write</code> method makes use of a temporary variable,
<code class="varname">chunkTotalAmount</code>, that stores the total of the trades
in the chunk. This is done to ensure that if a skip occurs in the
<code class="methodname">write</code> method, that the
<span class="property">totalAmount</span> will be left unchanged. It is only at
the end of the <code class="methodname">write</code> method, once we are
guaranteed that no exceptions will be thrown, that we update the
<code class="varname">totalAmount</code>.</p>
<p>In order for the <code class="methodname">writeFooter</code> method to be
called, the <code class="classname">TradeItemWriter</code> (which implements
<code class="classname">FlatFileFooterCallback</code>) must be wired into the
<code class="classname">FlatFileItemWriter</code> as the
<code class="code">footerCallback</code>:</p>
<pre class="programlisting">&lt;bean id="tradeItemWriter" class="..TradeItemWriter"&gt;
&lt;property name="delegate" ref="flatFileItemWriter" /&gt;
&lt;/bean&gt;
&lt;bean id="flatFileItemWriter" class="org.spr...FlatFileItemWriter"&gt;
&lt;property name="resource" ref="outputResource" /&gt;
&lt;property name="lineAggregator" ref="lineAggregator"/&gt;
<span class="bold"><strong> &lt;property name="footerCallback" ref="tradeItemWriter" /&gt;</strong></span>
&lt;/bean&gt;</pre>
<p>The way that the <code class="classname">TradeItemWriter</code> has been
so far will only function correctly if the <code class="classname">Step</code>
is not restartable. This is because the class is stateful (since it
stores the <code class="code">totalAmount</code>), but the <code class="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
<code class="classname">ItemStream</code> interface should be implemented along
with the methods <code class="methodname">open</code> and
<code class="methodname">update</code>:</p>
<pre class="programlisting">public void open(ExecutionContext executionContext) {
if (executionContext.containsKey("total.amount") {
totalAmount = (BigDecimal) executionContext.get("total.amount");
}
}
public void update(ExecutionContext executionContext) {
executionContext.put("total.amount", totalAmount);
}</pre>
<p>The <code class="methodname">update</code> method will store the most
current version of <code class="code">totalAmount</code> to the
<code class="classname">ExecutionContext</code> just before that object is
persisted to the database. The <code class="methodname">open</code> method will
retrieve any existing <code class="code">totalAmount</code> from the
<code class="classname">ExecutionContext</code> and use it as the starting point
for processing, allowing the <code class="classname">TradeItemWriter</code> to
pick up on restart where it left off the previous time the
<code class="classname">Step</code> was executed.</p>
</section>
</section><footer/></body></html>