BATCH-1270: Update documentation for formatting consistency
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE appendix PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||
<appendix>
|
||||
<title>List of ItemReaders</title>
|
||||
<appendix id="listOfReadersAndWriters">
|
||||
<title>List of ItemReaders and ItemWriters</title>
|
||||
|
||||
<section>
|
||||
<section id="itemReadersAppendix">
|
||||
<title>Item Readers</title>
|
||||
|
||||
<table>
|
||||
@@ -124,7 +124,7 @@
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="itemWritersAppendix">
|
||||
<title>Item Writers</title>
|
||||
|
||||
<table>
|
||||
@@ -194,7 +194,7 @@
|
||||
<row>
|
||||
<entry align="left">JdbcBatchItemWriter</entry>
|
||||
|
||||
<entry align="left">Uses batching freatures from a
|
||||
<entry align="left">Uses batching features from a
|
||||
<classname>PreparedStatement</classname>, if available, and can
|
||||
take rudimentary steps to locate a failure during a
|
||||
<methodname>flush</methodname>.</entry>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<classname>ItemWriter</classname> can implement a listener interface as
|
||||
well, if appropriate.</para>
|
||||
|
||||
<section>
|
||||
<section id="loggingItemProcessingAndFailures">
|
||||
<title>Logging Item Processing and Failures</title>
|
||||
|
||||
<para>A common use case is the need for special handling of errors in a
|
||||
@@ -53,13 +53,12 @@
|
||||
<para>Having implemented this listener it must be registered with the
|
||||
step:</para>
|
||||
|
||||
<programlisting><bean id="simpleStep"
|
||||
class="org.springframework.batch.core.step.item.SimpleStepFactoryBean" >
|
||||
<programlisting><step id="simpleStep">
|
||||
...
|
||||
<property name="listeners">
|
||||
<bean class="org.example...ItemFailureLoggerListener"/>
|
||||
</property>
|
||||
</bean></programlisting>
|
||||
<listeners>
|
||||
<listener class="org.example...ItemFailureLoggerListener"/>
|
||||
</listeners>
|
||||
</step></programlisting>
|
||||
|
||||
<para>Remember that if your listener does anything in an
|
||||
<code>onError()</code> method, it will be inside a transaction that is
|
||||
@@ -70,7 +69,7 @@
|
||||
REQUIRES_NEW.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="stoppingAJobManuallyForBusinessReasons">
|
||||
<title>Stopping a Job Manually for Business Reasons</title>
|
||||
|
||||
<para>Spring Batch provides a <methodname>stop</methodname>() method
|
||||
@@ -87,11 +86,9 @@
|
||||
<programlisting>public class PoisonPillItemWriter implements ItemWriter<T> {
|
||||
|
||||
public void write(T item) throws Exception {
|
||||
|
||||
if (isPoisonPill(item)) {
|
||||
throw new PoisonPillException("Posion pill detected: "+item);
|
||||
throw new PoisonPillException("Posion pill detected: " + item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}</programlisting>
|
||||
@@ -106,15 +103,11 @@
|
||||
public void setDelegate(ItemReader<T> delegate) { ... }
|
||||
|
||||
public T read() throws Exception {
|
||||
|
||||
T item = delegate.read();
|
||||
|
||||
if (isEndItem(item)) {
|
||||
return null; // end the step here
|
||||
}
|
||||
|
||||
return item;
|
||||
|
||||
}
|
||||
|
||||
}</programlisting>
|
||||
@@ -126,13 +119,14 @@
|
||||
injected into the <classname>Step</classname> through the
|
||||
<classname>SimpleStepFactoryBean</classname>:</para>
|
||||
|
||||
<programlisting><bean id="simpleStep"
|
||||
class="org.springframework.batch.core.step.item.SimpleStepFactoryBean" >
|
||||
...
|
||||
<property name="chunkCompletionPolicy">
|
||||
<bean class="org.example...SpecialCompletionPolicy"/>
|
||||
</property>
|
||||
</bean></programlisting>
|
||||
<programlisting><step id="simpleStep">
|
||||
<tasklet>
|
||||
<chunk reader="reader" writer="writer" commit-interval="10"
|
||||
<emphasis role="bold">chunk-completion-policy="completionPolicy"</emphasis>/>
|
||||
</tasklet>
|
||||
</step>
|
||||
|
||||
<bean id="completionPolicy" class="org.example...SpecialCompletionPolicy"/></programlisting>
|
||||
|
||||
<para>An alternative is to set a flag in the
|
||||
<classname>StepExecution</classname>, which is checked by the
|
||||
@@ -166,7 +160,7 @@
|
||||
an abnormal ending to a job.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="addingAFooterRecord">
|
||||
<title>Adding a Footer Record</title>
|
||||
|
||||
<para>Often when writing to flat files, a "footer" record must be appended
|
||||
@@ -177,25 +171,23 @@
|
||||
<classname>FlatFileHeaderCallback</classname>) are optional properties of
|
||||
the <classname>FlatFileItemWriter</classname>:</para>
|
||||
|
||||
<programlisting> <bean id="itemWriter"
|
||||
class="org.springframework.batch.item.file.FlatFileItemWriter">
|
||||
<programlisting><bean id="itemWriter" class="org.spr...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>
|
||||
</bean></programlisting>
|
||||
|
||||
<para>The footer callback interface is very simple. It has just one method
|
||||
that is called when the footer must be written:</para>
|
||||
|
||||
<programlisting> public interface FlatFileFooterCallback {
|
||||
<programlisting>public interface FlatFileFooterCallback {
|
||||
|
||||
void writeFooter(Writer writer) throws IOException;
|
||||
void writeFooter(Writer writer) throws IOException;
|
||||
|
||||
}</programlisting>
|
||||
}</programlisting>
|
||||
|
||||
<section>
|
||||
<section id="writingASummaryFooter">
|
||||
<title>Writing a Summary Footer</title>
|
||||
|
||||
<para>A very common requirement involving footer records is to aggregate
|
||||
@@ -208,10 +200,11 @@
|
||||
Trades is placed in a footer, then the following ItemWriter
|
||||
implementation can be used:</para>
|
||||
|
||||
<programlisting> public class TradeItemWriter implements ItemWriter<Trade>,
|
||||
FlatFileFooterCallback {
|
||||
<programlisting>public class TradeItemWriter implements ItemWriter<Trade>,
|
||||
FlatFileFooterCallback {
|
||||
|
||||
private ItemWriter<Trade> delegate;
|
||||
|
||||
private double totalAmount = 0.0;
|
||||
|
||||
public void write(List<? extends Trade> items) {
|
||||
@@ -226,7 +219,7 @@
|
||||
}
|
||||
|
||||
public void setDelegate(ItemWriter delegate) {...}
|
||||
}</programlisting>
|
||||
}</programlisting>
|
||||
|
||||
<para>This <classname>TradeItemWriter</classname> stores a
|
||||
<code>totalAmount</code> value that is increased with the
|
||||
@@ -238,17 +231,15 @@
|
||||
the <classname>FlatFileItemWriter</classname> as the
|
||||
<code>footerCallback</code>:</para>
|
||||
|
||||
<programlisting> <bean id="tradeItemWriter" class="..TradeItemWriter">
|
||||
<property name="delegate" ref="flatFileItemWriter" />
|
||||
</bean>
|
||||
<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>
|
||||
<bean id="flatFileItemWriter" class="org.spr...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>
|
||||
@@ -290,7 +281,7 @@
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="drivingQueryBasedItemReaders">
|
||||
<title>Driving Query Based ItemReaders</title>
|
||||
|
||||
<para>In the chapter on readers and writers, database input using paging
|
||||
@@ -347,10 +338,10 @@
|
||||
spanning multiple lines with multiple formats. The following excerpt from
|
||||
a file illustrates this:</para>
|
||||
|
||||
<programlisting> HEA;0013100345;2007-02-15
|
||||
NCU;Smith;Peter;;T;20014539;F
|
||||
BAD;;Oak Street 31/A;;Small Town;00235;IL;US
|
||||
FOT;2;2;267.34</programlisting>
|
||||
<programlisting>HEA;0013100345;2007-02-15
|
||||
NCU;Smith;Peter;;T;20014539;F
|
||||
BAD;;Oak Street 31/A;;Small Town;00235;IL;US
|
||||
FOT;2;2;267.34</programlisting>
|
||||
|
||||
<para>Everything between the line starting with 'HEA' and the line
|
||||
starting with 'FOT' is considered one record. There are a few
|
||||
@@ -376,22 +367,21 @@
|
||||
<classname>ItemReader</classname> should be implemented as a wrapper for
|
||||
the <classname>FlatFileItemReader</classname>.</para>
|
||||
|
||||
<programlisting> <bean id="itemReader"
|
||||
class="org.springframework.batch.sample.iosample.internal.MultiLineTradeItemReader">
|
||||
<programlisting><bean id="itemReader" class="org.spr...MultiLineTradeItemReader">
|
||||
<property name="delegate">
|
||||
<bean class="org.springframework.batch.item.file.FlatFileItemReader">
|
||||
<property name="resource" value="data/iosample/input/multiLine.txt" />
|
||||
<property name="lineMapper">
|
||||
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
|
||||
<property name="lineTokenizer" ref="orderFileTokenizer"/>
|
||||
<property name="fieldSetMapper">
|
||||
<bean class="org.springframework.batch.item.file.mapping.PassThroughFieldSetMapper" />
|
||||
<bean class="org.springframework.batch.item.file.FlatFileItemReader">
|
||||
<property name="resource" value="data/iosample/input/multiLine.txt" />
|
||||
<property name="lineMapper">
|
||||
<bean class="org.spr...DefaultLineMapper">
|
||||
<property name="lineTokenizer" ref="orderFileTokenizer"/>
|
||||
<property name="fieldSetMapper">
|
||||
<bean class="org.spr...PassThroughFieldSetMapper" />
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</bean>
|
||||
</property>
|
||||
</bean></programlisting>
|
||||
</bean></programlisting>
|
||||
|
||||
<para>To ensure that each line is tokenized properly, which is especially
|
||||
important for fixed length input, the
|
||||
@@ -402,17 +392,16 @@
|
||||
deliver a <classname>FieldSet</classname> for each line back to the
|
||||
wrapping <classname>ItemReader</classname>.</para>
|
||||
|
||||
<programlisting> <bean id="orderFileTokenizer"
|
||||
class="org.springframework.batch.io.file.transform.PatternMatchingCompositeLineTokenizer">
|
||||
<programlisting><bean id="orderFileTokenizer" class="org.spr...PatternMatchingCompositeLineTokenizer">
|
||||
<property name="tokenizers">
|
||||
<map>
|
||||
<entry key="HEA*" value-ref="headerRecordTokenizer" />
|
||||
<entry key="FOT*" value-ref="footerRecordTokenizer" />
|
||||
<entry key="NCU*" value-ref="customerLineTokenizer" />
|
||||
<entry key="BAD*" value-ref="billingAddressLineTokenizer" />
|
||||
</map>
|
||||
<map>
|
||||
<entry key="HEA*" value-ref="headerRecordTokenizer" />
|
||||
<entry key="FOT*" value-ref="footerRecordTokenizer" />
|
||||
<entry key="NCU*" value-ref="customerLineTokenizer" />
|
||||
<entry key="BAD*" value-ref="billingAddressLineTokenizer" />
|
||||
</map>
|
||||
</property>
|
||||
</bean></programlisting>
|
||||
</bean></programlisting>
|
||||
|
||||
<para>This wrapper will have to be able recognize the end of a record so
|
||||
that it can continually call <methodname>read()</methodname> on its
|
||||
@@ -422,38 +411,38 @@
|
||||
<classname>ItemProcessor</classname> and
|
||||
<classname>ItemWriter</classname>.</para>
|
||||
|
||||
<programlisting> private FlatFileItemReader<FieldSet> delegate;
|
||||
<programlisting>private FlatFileItemReader<FieldSet> delegate;
|
||||
|
||||
public Trade read() throws Exception {
|
||||
public Trade read() throws Exception {
|
||||
Trade t = null;
|
||||
|
||||
for (FieldSet line = null; (line = this.delegate.read()) != null;) {
|
||||
String prefix = line.readString(0);
|
||||
if (prefix.equals("HEA")) {
|
||||
t = new Trade(); // Record must start with header
|
||||
}
|
||||
else if (prefix.equals("NCU")) {
|
||||
Assert.notNull(t, "No header was found.");
|
||||
t.setLast(line.readString(1));
|
||||
t.setFirst(line.readString(2));
|
||||
...
|
||||
}
|
||||
else if (prefix.equals("BAD")) {
|
||||
Assert.notNull(t, "No header was found.");
|
||||
t.setCity(line.readString(4));
|
||||
t.setState(line.readString(6));
|
||||
...
|
||||
}
|
||||
else if (prefix.equals("FOT")) {
|
||||
return t; // Record must end with footer
|
||||
}
|
||||
String prefix = line.readString(0);
|
||||
if (prefix.equals("HEA")) {
|
||||
t = new Trade(); // Record must start with header
|
||||
}
|
||||
else if (prefix.equals("NCU")) {
|
||||
Assert.notNull(t, "No header was found.");
|
||||
t.setLast(line.readString(1));
|
||||
t.setFirst(line.readString(2));
|
||||
...
|
||||
}
|
||||
else if (prefix.equals("BAD")) {
|
||||
Assert.notNull(t, "No header was found.");
|
||||
t.setCity(line.readString(4));
|
||||
t.setState(line.readString(6));
|
||||
...
|
||||
}
|
||||
else if (prefix.equals("FOT")) {
|
||||
return t; // Record must end with footer
|
||||
}
|
||||
}
|
||||
Assert.isNull(t, "No 'END' was found.");
|
||||
return null;
|
||||
}</programlisting>
|
||||
}</programlisting>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="executingSystemCommands">
|
||||
<title>Executing System Commands</title>
|
||||
|
||||
<para>Many batch jobs may require that an external command be called from
|
||||
@@ -466,15 +455,15 @@
|
||||
<classname>Tasklet</classname> implementation for calling system
|
||||
commands:</para>
|
||||
|
||||
<programlisting> <bean class="org.springframework.batch.core.step.tasklet.SystemCommandTasklet">
|
||||
<programlisting><bean class="org.springframework.batch.core.step.tasklet.SystemCommandTasklet">
|
||||
<property name="command" value="echo hello" />
|
||||
<!-- 5 second timeout for the command to complete -->
|
||||
<property name="timeout" value="5000" />
|
||||
</bean></programlisting>
|
||||
</bean></programlisting>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Handling Step completion when no input is found</title>
|
||||
<section id="handlingStepCompletionWhenNoInputIsFound">
|
||||
<title>Handling Step Completion When No Input is Found</title>
|
||||
|
||||
<para>In many batch scenarios, finding no rows in a database or file to
|
||||
process is not exceptional. The <classname>Step</classname> is simply
|
||||
@@ -490,17 +479,16 @@
|
||||
a common use case, a listener is provided with just this
|
||||
functionality:</para>
|
||||
|
||||
<programlisting>
|
||||
public class NoWorkFoundStepExecutionListener extends StepExecutionListenerSupport {
|
||||
<programlisting>public class NoWorkFoundStepExecutionListener extends StepExecutionListenerSupport {
|
||||
|
||||
public ExitStatus afterStep(StepExecution stepExecution) {
|
||||
if (stepExecution.getReadCount() == 0) {
|
||||
return ExitStatus.FAILED;
|
||||
public ExitStatus afterStep(StepExecution stepExecution) {
|
||||
if (stepExecution.getReadCount() == 0) {
|
||||
return ExitStatus.FAILED;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
</programlisting>
|
||||
}</programlisting>
|
||||
|
||||
<para>The above <classname>StepExecutionListener</classname> inspects the
|
||||
readCount property of the <classname>StepExecution</classname> during the
|
||||
@@ -511,8 +499,8 @@
|
||||
<classname>Step</classname>.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Passing data to future steps</title>
|
||||
<section id="passingDataToFutureSteps">
|
||||
<title>Passing Data to Future Steps</title>
|
||||
|
||||
<para>It is often useful to pass information from one step to another.
|
||||
This can be done using the <classname>ExecutionContext</classname>. The
|
||||
@@ -580,8 +568,7 @@
|
||||
</step>
|
||||
</job>
|
||||
|
||||
<emphasis role="bold"><beans:bean id="promotionListener"
|
||||
class="org.spr....ExecutionContextPromotionListener">
|
||||
<emphasis role="bold"><beans:bean id="promotionListener" class="org.spr....ExecutionContextPromotionListener">
|
||||
<beans:property name="keys" value="someKey"/>
|
||||
</beans:bean></emphasis></programlisting>
|
||||
|
||||
@@ -597,7 +584,8 @@
|
||||
|
||||
@BeforeStep
|
||||
public void saveStepExecution(StepExecution stepExecution) {
|
||||
ExecutionContext jobContext = stepExecution.getJobExecution().getExecutionContext();
|
||||
JobExecution jobExecution = stepExecution.getJobExecution();
|
||||
ExecutionContext jobContext = jobExecution.getExecutionContext();
|
||||
this.someObject = jobContext.get("someKey");
|
||||
}
|
||||
}</programlisting>
|
||||
|
||||
@@ -218,7 +218,7 @@
|
||||
controlled and persisted:</para>
|
||||
|
||||
<table>
|
||||
<title>JobExecution properties</title>
|
||||
<title>JobExecution Properties</title>
|
||||
|
||||
<tgroup cols="2">
|
||||
<colspec colname="c1" colwidth="*" />
|
||||
@@ -592,7 +592,7 @@
|
||||
<classname>StepExecution</classname>:</para>
|
||||
|
||||
<table>
|
||||
<title>StepExecution properties</title>
|
||||
<title>StepExecution Properties</title>
|
||||
|
||||
<tgroup cols="2">
|
||||
<colspec colname="c1" colwidth="*" />
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
|
||||
<chapter id="configureJob">
|
||||
<title>Configuring and Running A Job</title>
|
||||
<title>Configuring and Running a Job</title>
|
||||
|
||||
<para>In <xref linkend="domain" />, the overall architecture design was
|
||||
discussed, using the following diagram as a guide:</para>
|
||||
@@ -27,7 +27,7 @@
|
||||
stored during that run. This chapter will explain the various configuration
|
||||
options and runtime concerns of a <classname>Job</classname>.</para>
|
||||
|
||||
<section>
|
||||
<section id="configuringAJob">
|
||||
<title>Configuring a Job</title>
|
||||
|
||||
<para>There are multiple implementations of the <link
|
||||
@@ -52,10 +52,10 @@
|
||||
<step id="playerSummarization" parent="s3"/>
|
||||
</job></programlisting>
|
||||
|
||||
<section>
|
||||
<section id="restartability">
|
||||
<title>Restartability</title>
|
||||
|
||||
<para>One key issue when execution a batch job concerns the behavior of
|
||||
<para>One key issue when executing a batch job concerns the behavior of
|
||||
a <classname>Job</classname> when it is restarted? The launching of a
|
||||
<classname>Job</classname> is considered to be a 'restart' if a
|
||||
<classname>JobExecution</classname> already exists for the particular
|
||||
@@ -99,8 +99,8 @@ catch (JobRestartException e) {
|
||||
attempt will throw a <classname>JobRestartException</classname>.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Intercepting Job execution</title>
|
||||
<section id="interceptingJobExecution">
|
||||
<title>Intercepting Job Execution</title>
|
||||
|
||||
<para>During the course of the execution of a
|
||||
<classname>Job</classname>, it may be useful to be notified of various
|
||||
@@ -156,7 +156,7 @@ catch (JobRestartException e) {
|
||||
</itemizedlist>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="inheritingFromAParentJob">
|
||||
<title>Inheriting from a Parent Job</title>
|
||||
|
||||
<para>If a group of <classname>Job</classname>s share similar, but not
|
||||
@@ -193,7 +193,7 @@ catch (JobRestartException e) {
|
||||
for more detailed information.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="jobFactoryAndStatefulComponentsInSteps">
|
||||
<title>JobFactory and Stateful Components in Steps</title>
|
||||
|
||||
<para>Unlike many traditional Spring applications, many of the
|
||||
@@ -221,7 +221,7 @@ catch (JobRestartException e) {
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="configuringJobRepository">
|
||||
<title>Configuring a JobRepository</title>
|
||||
|
||||
<para>As described in earlier, the <link
|
||||
@@ -239,7 +239,7 @@ catch (JobRestartException e) {
|
||||
<programlisting><job-repository id="jobRepository"
|
||||
dataSource="dataSource"
|
||||
transactionManager="transactionManager"
|
||||
isolation-level-for-create="serializable"
|
||||
isolation-level-for-create="SERIALIZABLE"
|
||||
table-prefix="BATCH_"
|
||||
/></programlisting>
|
||||
|
||||
@@ -247,8 +247,8 @@ catch (JobRestartException e) {
|
||||
the id. If they are not set, the defaults shown above will be used. They
|
||||
are shown above for awareness purposes.</para>
|
||||
|
||||
<section>
|
||||
<title>Transaction Configuration For the JobRepository</title>
|
||||
<section id="txConfigForJobRepository">
|
||||
<title>Transaction Configuration for the JobRepository</title>
|
||||
|
||||
<para>If the namespace is used, transactional advice will be
|
||||
automatically created around the repository. This is to ensure that the
|
||||
@@ -292,7 +292,7 @@ catch (JobRestartException e) {
|
||||
</section>
|
||||
|
||||
<section id="repositoryTablePrefix">
|
||||
<title>Changing the table prefix</title>
|
||||
<title>Changing the Table Prefix</title>
|
||||
|
||||
<para>Another modifiable property of the
|
||||
<classname>JobRepository</classname> is the table prefix of the
|
||||
@@ -316,7 +316,7 @@ catch (JobRestartException e) {
|
||||
</note>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="inMemoryRepository">
|
||||
<title>In-Memory Repository</title>
|
||||
|
||||
<para>There are scenarios in which you may not want to persist your
|
||||
@@ -344,7 +344,7 @@ catch (JobRestartException e) {
|
||||
<classname>ResourcelessTransactionManager</classname> useful.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="nonStandardDatabaseTypesInRepository">
|
||||
<title>Non-standard Database Types in a Repository</title>
|
||||
|
||||
<para>If you are using a database platform that is not in the list of
|
||||
@@ -447,7 +447,7 @@ catch (JobRestartException e) {
|
||||
executed.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="runningAJob">
|
||||
<title>Running a Job</title>
|
||||
|
||||
<para>At a minimum, launching a batch job requires two things: the
|
||||
@@ -461,7 +461,7 @@ catch (JobRestartException e) {
|
||||
<classname>JobLauncher</classname>, configured for asynchronous job
|
||||
launching, that multiple requests will invoke to launch their jobs.</para>
|
||||
|
||||
<section>
|
||||
<section id="runningJobsFromCommandLine">
|
||||
<title>Running Jobs from the Command Line</title>
|
||||
|
||||
<para>For users that want to run their jobs from an enterprise
|
||||
@@ -473,7 +473,7 @@ catch (JobRestartException e) {
|
||||
even 'build tools' such as ant or maven. However, because most people
|
||||
are familiar with shell scripts, this example will focus on them.</para>
|
||||
|
||||
<section>
|
||||
<section id="commandLineJobRunner">
|
||||
<title>The CommandLineJobRunner</title>
|
||||
|
||||
<para>Because the script launching the job must kick off a Java
|
||||
@@ -565,7 +565,7 @@ catch (JobRestartException e) {
|
||||
<classname>JobLauncher</classname></para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="exitCodes">
|
||||
<title>ExitCodes</title>
|
||||
|
||||
<para>When launching a batch job from the command-line, an enterprise
|
||||
@@ -623,8 +623,8 @@ catch (JobRestartException e) {
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Running Jobs from within a web container</title>
|
||||
<section id="runningJobsFromWebContainer">
|
||||
<title>Running Jobs from within a Web Container</title>
|
||||
|
||||
<para>Historically, offline processing such as batch jobs have been
|
||||
launched from the command-line, as described above. However, there are
|
||||
@@ -719,8 +719,8 @@ public class JobLauncherController {
|
||||
below, add additional functionality for querying and controlling the meta
|
||||
data.</para>
|
||||
|
||||
<section>
|
||||
<title>Querying the repository</title>
|
||||
<section id="queryingRepository">
|
||||
<title>Querying the Repository</title>
|
||||
|
||||
<para>The most basic need before any advanced features is the ability to
|
||||
query the repository for existing executions. This functionality is
|
||||
@@ -761,7 +761,7 @@ public class JobLauncherController {
|
||||
p:dataSource-ref="dataSource" <emphasis role="bold">p:tablePrefix="BATCH_" </emphasis>/></programlisting>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="JobOperator">
|
||||
<title>JobOperator</title>
|
||||
|
||||
<para>As previously discussed, the <classname>JobRepository</classname>
|
||||
@@ -827,7 +827,7 @@ public class JobLauncherController {
|
||||
</bean></programlisting>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="JobParametersIncrementer">
|
||||
<title>JobParametersIncrementer</title>
|
||||
|
||||
<para>Most of the methods on <classname>JobOperator</classname> are
|
||||
@@ -894,7 +894,7 @@ public class JobLauncherController {
|
||||
</job></programlisting>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="stoppingAJob">
|
||||
<title>Stopping a Job</title>
|
||||
|
||||
<para>One of the most common use cases of
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<classname>ItemReader</classname>, <classname>ItemProcessor</classname> and
|
||||
<classname>ItemWriter</classname>.</para>
|
||||
|
||||
<section>
|
||||
<section id="itemReader">
|
||||
<title id="infrastructure.1">ItemReader</title>
|
||||
|
||||
<para>Although a simple concept, an <classname>ItemReader</classname> is
|
||||
@@ -70,7 +70,7 @@
|
||||
the first invocation of <methodname>read</methodname>.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="itemWriter">
|
||||
<title id="infrastructure.1.4">ItemWriter</title>
|
||||
|
||||
<para><classname>ItemWriter</classname> is similar in functionality to an
|
||||
@@ -103,7 +103,7 @@
|
||||
Session before returning.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="itemProcessor">
|
||||
<title>ItemProcessor</title>
|
||||
|
||||
<para>The <classname>ItemReader</classname> and
|
||||
@@ -200,7 +200,7 @@ public class BarWriter implements ItemWriter<Bar>{
|
||||
</step>
|
||||
</job></programlisting>
|
||||
|
||||
<section>
|
||||
<section id="chainingItemProcessors">
|
||||
<title>Chaining ItemProcessors</title>
|
||||
|
||||
<para>Performing a single transformation is useful in many scenarios,
|
||||
@@ -274,7 +274,7 @@ compositeProcessor.setItemProcessors(itemProcessors);</programlisting>
|
||||
</bean></programlisting>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="filiteringRecords">
|
||||
<title>Filtering Records</title>
|
||||
|
||||
<para>One typical use for an item processor is to filter out records
|
||||
@@ -350,7 +350,7 @@ compositeProcessor.setItemProcessors(itemProcessors);</programlisting>
|
||||
Quartz <classname>JobDataMap</classname>.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="delegatePatternAndRegistering">
|
||||
<title>The Delegate Pattern and Registering with the Step</title>
|
||||
|
||||
<para>Note that the <classname>CompositeItemWriter</classname> is an
|
||||
@@ -387,7 +387,7 @@ compositeProcessor.setItemProcessors(itemProcessors);</programlisting>
|
||||
<bean id="barWriter" class="...BarWriter" /></programlisting>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="flatFiles">
|
||||
<title id="infrastructure.1.2">Flat Files</title>
|
||||
|
||||
<para>One of the most common mechanisms for interchanging bulk data has
|
||||
@@ -398,7 +398,7 @@ compositeProcessor.setItemProcessors(itemProcessors);</programlisting>
|
||||
files are those in which fields are separated by a delimiter, such as a
|
||||
comma. Fixed Length files have fields that are a set length.</para>
|
||||
|
||||
<section>
|
||||
<section id="fieldSet">
|
||||
<title>The FieldSet</title>
|
||||
|
||||
<para>When working with flat files in Spring Batch, regardless of
|
||||
@@ -562,7 +562,7 @@ boolean booleanValue = fs.readBoolean(2);</programlisting>
|
||||
</tgroup>
|
||||
</table></para>
|
||||
|
||||
<section>
|
||||
<section id="lineMapper">
|
||||
<title>LineMapper</title>
|
||||
|
||||
<para>As with <classname>RowMapper</classname>, which takes a low
|
||||
@@ -590,7 +590,7 @@ boolean booleanValue = fs.readBoolean(2);</programlisting>
|
||||
mapped to an object, as described below.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="lineTokenizer">
|
||||
<title>LineTokenizer</title>
|
||||
|
||||
<para>An abstraction for turning a line of input into a line into a
|
||||
@@ -636,7 +636,7 @@ boolean booleanValue = fs.readBoolean(2);</programlisting>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="fieldSetMapper">
|
||||
<title>FieldSetMapper</title>
|
||||
|
||||
<para>The <classname>FieldSetMapper</classname> interface defines a
|
||||
@@ -659,7 +659,7 @@ boolean booleanValue = fs.readBoolean(2);</programlisting>
|
||||
<classname>JdbcTemplate</classname>.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="defaultLineMapper">
|
||||
<title>DefaultLineMapper</title>
|
||||
|
||||
<para>Now that the basic interfaces for reading in flat files have
|
||||
@@ -721,7 +721,7 @@ boolean booleanValue = fs.readBoolean(2);</programlisting>
|
||||
to the raw line is needed.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="simpleDelimitedFileReadingExample">
|
||||
<title>Simple Delimited File Reading Example</title>
|
||||
|
||||
<para>The following example will be used to illustrate this using an
|
||||
@@ -794,8 +794,8 @@ Player player = itemReader.read();</programlisting>
|
||||
reached, null will be returned.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Mapping fields by name</title>
|
||||
<section id="mappingFieldsByName">
|
||||
<title>Mapping Fields by Name</title>
|
||||
|
||||
<para>There is one additional piece of functionality that is allowed
|
||||
by both <classname>DelimitedLineTokenizer</classname> and
|
||||
@@ -808,8 +808,8 @@ Player player = itemReader.read();</programlisting>
|
||||
|
||||
<para><programlisting>tokenizer.setNames(new String[] {"ID", "lastName","firstName","position","birthYear","debutYear"}); </programlisting></para>
|
||||
|
||||
<para>a <classname>FieldSetMapper</classname> can this use this
|
||||
information as follows:</para>
|
||||
<para>A <classname>FieldSetMapper</classname> can use this information
|
||||
as follows:</para>
|
||||
|
||||
<para><programlisting>public class PlayerMapper implements FieldSetMapper<Player> {
|
||||
public Player mapFieldSet(FieldSet fs) {
|
||||
@@ -831,7 +831,7 @@ Player player = itemReader.read();</programlisting>
|
||||
}</programlisting></para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="beanWrapperFieldSetMapper">
|
||||
<title>Automapping FieldSets to Domain Objects</title>
|
||||
|
||||
<para>For many, having to write a specific
|
||||
@@ -863,8 +863,8 @@ Player player = itemReader.read();</programlisting>
|
||||
required.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Fixed Length file formats</title>
|
||||
<section id="fixedLengthFileFormats">
|
||||
<title>Fixed Length File Formats</title>
|
||||
|
||||
<para>So far only delimited files have been discussed in much detail,
|
||||
however, they represent only half of the file reading picture. Many
|
||||
@@ -930,7 +930,7 @@ UK21341EAH4521535.11customer5</programlisting>
|
||||
</section>
|
||||
|
||||
<section id="prefixMatchingLineMapper">
|
||||
<title>Multiple record types within a single file</title>
|
||||
<title>Multiple Record Types within a Single File</title>
|
||||
|
||||
<para>All of the file reading examples up to this point have all made
|
||||
a key assumption for simplicity's sake: all of the records in a file
|
||||
@@ -1003,12 +1003,12 @@ LINEB;2134776319DEF422.99M005LI</programlisting>
|
||||
|
||||
<para>It is also common for a flat file to contain records that each
|
||||
span multiple lines. To handle this situation, a more complex strategy
|
||||
is required. A demonstration of this common patter can be found in
|
||||
is required. A demonstration of this common pattern can be found in
|
||||
<xref linkend="multiLineRecords" />.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Exception Handling in flat files</title>
|
||||
<section id="exceptionHandlingInFlatFiles">
|
||||
<title>Exception Handling in Flat Files</title>
|
||||
|
||||
<para>There are many scenarios when tokenizing a line may cause
|
||||
exceptions to be thrown. Many flat files are imperfect and contain
|
||||
@@ -1027,7 +1027,7 @@ LINEB;2134776319DEF422.99M005LI</programlisting>
|
||||
and indicates a more specific error encountered while
|
||||
tokenizing.</para>
|
||||
|
||||
<section>
|
||||
<section id="incorrectTokenCountException">
|
||||
<title>IncorrectTokenCountException</title>
|
||||
|
||||
<para>Both <classname>DelimitedLineTokenizer</classname> and
|
||||
@@ -1056,7 +1056,7 @@ catch(IncorrectTokenCountException e){
|
||||
thrown.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="incorrectLineLengthException">
|
||||
<title>IncorrectLineLengthException</title>
|
||||
|
||||
<para>Files formatted in a fixed length format have additional
|
||||
@@ -1105,7 +1105,7 @@ assertEquals("", tokens.readString(1));</programlisting>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="flatFileItemWriter">
|
||||
<title>FlatFileItemWriter</title>
|
||||
|
||||
<para>Writing out to flat files has the same problems and issues that
|
||||
@@ -1113,7 +1113,7 @@ assertEquals("", tokens.readString(1));</programlisting>
|
||||
in either delimited or fixed length formats in a transactional
|
||||
manner.</para>
|
||||
|
||||
<section>
|
||||
<section id="lineAggregator">
|
||||
<title>LineAggregator</title>
|
||||
|
||||
<para>Just as the <classname>LineTokenizer</classname> interface is
|
||||
@@ -1138,7 +1138,7 @@ assertEquals("", tokens.readString(1));</programlisting>
|
||||
<classname>item</classname> and returns a
|
||||
<classname>String</classname>.</para>
|
||||
|
||||
<section>
|
||||
<section id="PassThroughLineAggregator">
|
||||
<title>PassThroughLineAggregator</title>
|
||||
|
||||
<para>The most basic implementation of the LineAggregator interface
|
||||
@@ -1160,7 +1160,7 @@ assertEquals("", tokens.readString(1));</programlisting>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="SimplifiedFileWritingExample">
|
||||
<title>Simplified File Writing Example</title>
|
||||
|
||||
<para>Now that the <classname>LineAggregator</classname> interface and
|
||||
@@ -1191,17 +1191,15 @@ assertEquals("", tokens.readString(1));</programlisting>
|
||||
|
||||
<para>A simple configuration would look like the following:</para>
|
||||
|
||||
<programlisting><bean id="itemWriter"
|
||||
class="org.springframework.batch.io.file.FlatFileItemWriter">
|
||||
<property name="resource"
|
||||
value="file:target/test-outputs/20070122.testStream.multilineStep.txt" />
|
||||
<programlisting><bean id="itemWriter" class="org.spr...FlatFileItemWriter">
|
||||
<property name="resource" value="file:target/test-outputs/output.txt" />
|
||||
<property name="lineAggregator">
|
||||
<bean class="org.spr...PassThroughLineAggregator"/>
|
||||
</property>
|
||||
</bean></programlisting>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="FieldExtractor">
|
||||
<title>FieldExtractor</title>
|
||||
|
||||
<para>The above example may be useful for the most basic uses of a
|
||||
@@ -1259,23 +1257,22 @@ assertEquals("", tokens.readString(1));</programlisting>
|
||||
object, which can then be written out with a delimiter between the
|
||||
elements, or as part of a field-width line.</para>
|
||||
|
||||
<section>
|
||||
<section id="PassThroughFieldExtractor">
|
||||
<title>PassThroughFieldExtractor</title>
|
||||
|
||||
<para>There are many cases where an array or something that can be
|
||||
converted to an array, such as a <classname>Collection</classname>,
|
||||
needs to be written out. For example, a <classname>List</classname>
|
||||
could be passed through, in which case it only needs to be converted
|
||||
to an <classname>Object</classname> array to be written out. For
|
||||
this type of scenario the
|
||||
<classname>PassThroughFieldExtractor</classname> can be used. It
|
||||
should be noted, that if the object passed in is not an array, and
|
||||
not a <classname>Collection</classname>, then an
|
||||
<classname>Object</classname> array containing solely the item will
|
||||
be returned.</para>
|
||||
<para>There are many cases where a collection, such as an array,
|
||||
<classname>Collection</classname>, or
|
||||
<classname>FieldSet</classname>, needs to be written out.
|
||||
"Extracting" an array from a one of these collection types is very
|
||||
straightforward: simply convert the collection to an array.
|
||||
Therefore, the <classname>PassThroughFieldExtractor</classname>
|
||||
should be used in this scenario. It should be noted, that if the
|
||||
object passed in is not a type of collection, then the
|
||||
<classname>PassThroughFieldExtractor</classname> will return an
|
||||
array containing solely the item to be extracted.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="BeanWrapperFieldExtractor">
|
||||
<title>BeanWrapperFieldExtractor</title>
|
||||
|
||||
<para>As with the <classname>BeanWrapperFieldSetMapper</classname>
|
||||
@@ -1311,7 +1308,7 @@ assertEquals(born, values[2]);</programlisting>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="delimitedFileWritingExample">
|
||||
<title>Delimited File Writing Example</title>
|
||||
|
||||
<para>The most basic flat file format is one in which all fields are
|
||||
@@ -1354,7 +1351,7 @@ assertEquals(born, values[2]);</programlisting>
|
||||
then written out with commas between each field.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="fixedWidthFileWritingExample">
|
||||
<title>Fixed Width File Writing Example</title>
|
||||
|
||||
<para>Delimited is not the only type of flat file format. Many prefer
|
||||
@@ -1392,8 +1389,8 @@ assertEquals(born, values[2]);</programlisting>
|
||||
url="http://java.sun.com/j2se/1.5.0/docs/api/java/util/Formatter.html"><citetitle>Formatter</citetitle></ulink>.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Handling file creation</title>
|
||||
<section id="handlingFileCreation">
|
||||
<title>Handling File Creation</title>
|
||||
|
||||
<para><classname>FlatFileItemReader</classname> has a very simple
|
||||
relationship with file resources. When the reader is initialized, it
|
||||
@@ -1416,7 +1413,7 @@ assertEquals(born, values[2]);</programlisting>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="xmlReadingWriting">
|
||||
<title id="infrastructure.2.3">XML Item Readers and Writers</title>
|
||||
|
||||
<para>Spring Batch provides transactional infrastructure for both reading
|
||||
@@ -1482,7 +1479,7 @@ assertEquals(born, values[2]);</programlisting>
|
||||
<para>Now with an introduction to OXM and how one can use XML fragments to
|
||||
represent records, let's take a closer look at readers and writers.</para>
|
||||
|
||||
<section>
|
||||
<section id="StaxEventItemReader">
|
||||
<title>StaxEventItemReader</title>
|
||||
|
||||
<para>The <classname>StaxEventItemReader</classname> configuration
|
||||
@@ -1618,7 +1615,7 @@ while (hasNext) {
|
||||
}</programlisting></para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="StaxEventItemWriter">
|
||||
<title>StaxEventItemWriter</title>
|
||||
|
||||
<para>Output works symmetrically to input. The
|
||||
@@ -1686,7 +1683,7 @@ staxItemWriter.write(trade);</programlisting>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="multiFileInput">
|
||||
<title>Multi-File Input</title>
|
||||
|
||||
<para>It is a common requirement to process multiple files within a single
|
||||
@@ -1716,7 +1713,7 @@ staxItemWriter.write(trade);</programlisting>
|
||||
individual directories until completed successfully.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="database">
|
||||
<title id="infrastructure.2.2">Database</title>
|
||||
|
||||
<para>Like most enterprise application styles, a database is the central
|
||||
@@ -1734,7 +1731,7 @@ staxItemWriter.write(trade);</programlisting>
|
||||
provides two types of solutions for this problem: Cursor and Paging
|
||||
database ItemReaders.</para>
|
||||
|
||||
<section>
|
||||
<section id="cursorBasedItemReaders">
|
||||
<title>Cursor Based ItemReaders</title>
|
||||
|
||||
<para>Using a database cursor is generally the default approach of most
|
||||
@@ -1782,7 +1779,7 @@ staxItemWriter.write(trade);</programlisting>
|
||||
collected (assuming no instance variables are maintaining references to
|
||||
them).</para>
|
||||
|
||||
<section>
|
||||
<section id="JdbcCursorItemReader">
|
||||
<title>JdbcCursorItemReader</title>
|
||||
|
||||
<para><classname>JdbcCursorItemReader</classname> is the Jdbc
|
||||
@@ -1879,7 +1876,7 @@ itemReader.close(executionContext);</programlisting>
|
||||
</property>
|
||||
</bean></programlisting>
|
||||
|
||||
<section>
|
||||
<section id="JdbcCursorItemReaderProperties">
|
||||
<title>Additional Properties</title>
|
||||
|
||||
<para>Because there are so many varying options for opening a cursor
|
||||
@@ -1990,7 +1987,7 @@ itemReader.close(executionContext);</programlisting>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="HibernateCursorItemReader">
|
||||
<title>HibernateCursorItemReader</title>
|
||||
|
||||
<para>Just as normal Spring users make important decisions about
|
||||
@@ -2050,7 +2047,7 @@ itemReader.close(executionContext);</programlisting>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="pagingItemReaders">
|
||||
<title>Paging ItemReaders</title>
|
||||
|
||||
<para>An alternative to using a database cursor is executing multiple
|
||||
@@ -2059,7 +2056,7 @@ itemReader.close(executionContext);</programlisting>
|
||||
specify the starting row number and the number of rows that we want
|
||||
returned for the page.</para>
|
||||
|
||||
<section>
|
||||
<section id="JdbcPagingItemReader">
|
||||
<title>JdbcPagingItemReader</title>
|
||||
|
||||
<para>One implementation of a paging <classname>ItemReader</classname>
|
||||
@@ -2122,7 +2119,7 @@ itemReader.close(executionContext);</programlisting>
|
||||
1.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="JpaPagingItemReader">
|
||||
<title>JpaPagingItemReader</title>
|
||||
|
||||
<para>Another implementation of a paging
|
||||
@@ -2159,7 +2156,7 @@ itemReader.close(executionContext);</programlisting>
|
||||
entities read from the database for each query execution.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="IbatisPagingItemReader">
|
||||
<title>IbatisPagingItemReader</title>
|
||||
|
||||
<para>If you use IBATIS for your data access then you can use the
|
||||
@@ -2207,7 +2204,7 @@ itemReader.close(executionContext);</programlisting>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="databaseItemWriters">
|
||||
<title>Database ItemWriters</title>
|
||||
|
||||
<para>While both Flat Files and XML have specific ItemWriters, there is
|
||||
@@ -2279,7 +2276,7 @@ itemReader.close(executionContext);</programlisting>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="reusingExistingServices">
|
||||
<title>Reusing Existing Services</title>
|
||||
|
||||
<para>Batch systems are often used in conjunction with other application
|
||||
@@ -2325,7 +2322,7 @@ itemReader.close(executionContext);</programlisting>
|
||||
</programlisting>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="validatingInput">
|
||||
<title id="infrastructure.5">Validating Input</title>
|
||||
|
||||
<para>During the course of this chapter, multiple approaches to parsing
|
||||
@@ -2391,7 +2388,7 @@ itemReader.close(executionContext);</programlisting>
|
||||
</section>
|
||||
|
||||
<section id="process-indicator">
|
||||
<title>Preventing state persistence</title>
|
||||
<title>Preventing State Persistence</title>
|
||||
|
||||
<para>By default, all of the <classname>ItemReader</classname> and
|
||||
<classname>ItemWriter</classname> implementations store their current
|
||||
@@ -2432,7 +2429,7 @@ itemReader.close(executionContext);</programlisting>
|
||||
executions in which it participates.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="customReadersWriters">
|
||||
<title id="infrastructure.1.1">Creating Custom ItemReaders and
|
||||
ItemWriters</title>
|
||||
|
||||
@@ -2447,7 +2444,7 @@ itemReader.close(executionContext);</programlisting>
|
||||
implement <classname>ItemStream</classname>, in order to illustrate how to
|
||||
make a reader or writer restartable.</para>
|
||||
|
||||
<section>
|
||||
<section id="customReader">
|
||||
<title>Custom ItemReader Example</title>
|
||||
|
||||
<para>For the purpose of this example, a simple
|
||||
@@ -2490,9 +2487,9 @@ assertEquals("2", itemReader.read());
|
||||
assertEquals("3", itemReader.read());
|
||||
assertNull(itemReader.read());</programlisting>
|
||||
|
||||
<section>
|
||||
<section id="restartableReader">
|
||||
<title>Making the <classname>ItemReader</classname>
|
||||
restartable</title>
|
||||
Restartable</title>
|
||||
|
||||
<para>The final challenge now is to make the
|
||||
<classname>ItemReader</classname> restartable. Currently, if the power
|
||||
@@ -2591,7 +2588,7 @@ assertEquals("2", itemReader.read());</programlisting>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="customWriter">
|
||||
<title>Custom ItemWriter Example</title>
|
||||
|
||||
<para>Implementing a Custom <classname>ItemWriter</classname> is similar
|
||||
@@ -2615,9 +2612,9 @@ assertEquals("2", itemReader.read());</programlisting>
|
||||
}
|
||||
}</programlisting>
|
||||
|
||||
<section>
|
||||
<section id="restartableWriter">
|
||||
<title>Making the <classname>ItemWriter</classname>
|
||||
restartable</title>
|
||||
Restartable</title>
|
||||
|
||||
<para>To make the ItemWriter restartable we would follow the same
|
||||
process as for the <classname>ItemReader</classname>, adding and
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
|
||||
<chapter>
|
||||
<chapter id="repeat">
|
||||
<title>Repeat</title>
|
||||
|
||||
<section>
|
||||
<section id="repeatTemplate">
|
||||
<title>RepeatTemplate</title>
|
||||
|
||||
<para>Batch processing is about repetitive actions - either as a simple
|
||||
optimisation, or as part of a job. To strategize and generalize the
|
||||
optimization, or as part of a job. To strategize and generalize the
|
||||
repetition as well as to provide what amounts to an iterator framework,
|
||||
Spring Batch has the <classname>RepeatOperations</classname> interface.
|
||||
The <classname>RepeatOperations</classname> interface looks like
|
||||
@@ -64,7 +64,7 @@ template.iterate(new RepeatCallback() {
|
||||
completion decision is delegated to an external policy as in the case
|
||||
above.</para>
|
||||
|
||||
<section>
|
||||
<section id="repeatContext">
|
||||
<title>RepeatContext</title>
|
||||
|
||||
<para>The method parameter for the <classname>RepeatCallback</classname>
|
||||
@@ -78,11 +78,11 @@ template.iterate(new RepeatCallback() {
|
||||
if there is a nested iteration in progress. The parent context is
|
||||
occasionally useful for storing data that need to be shared between
|
||||
calls to <methodname>iterate</methodname>. This is the case for instance
|
||||
if you want to count the number of occurrences of an even in the
|
||||
if you want to count the number of occurrences of an event in the
|
||||
iteration and remember it across subsequent calls.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="repeatStatus">
|
||||
<title>RepeatStatus</title>
|
||||
|
||||
<para><classname>RepeatStatus</classname> is an enumeration used by
|
||||
@@ -90,7 +90,7 @@ template.iterate(new RepeatCallback() {
|
||||
possible <classname>RepeatStatus</classname> values:</para>
|
||||
|
||||
<table>
|
||||
<title>ExitStatus properties</title>
|
||||
<title>ExitStatus Properties</title>
|
||||
|
||||
<tgroup cols="2">
|
||||
<tbody>
|
||||
@@ -124,13 +124,13 @@ template.iterate(new RepeatCallback() {
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="completionPolicies">
|
||||
<title>Completion Policies</title>
|
||||
|
||||
<para>Inside a <classname>RepeatTemplate</classname> the termination of
|
||||
the loop in the <methodname>iterate</methodname> method is determined by a
|
||||
<classname>CompletionPolicy</classname> which is also a factory for the
|
||||
<classname>ReapeatContext</classname>. The
|
||||
<classname>RepeatContext</classname>. The
|
||||
<classname>RepeatTemplate</classname> has the responsibility to use the
|
||||
current policy to create a <classname>RepeatContext</classname> and pass
|
||||
that in to the <classname>RepeatCallback</classname> at every stage in the
|
||||
@@ -153,7 +153,7 @@ template.iterate(new RepeatCallback() {
|
||||
would require a custom policy.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="repeatExceptionHandling">
|
||||
<title>Exception Handling</title>
|
||||
|
||||
<para>If there is an exception thrown inside a
|
||||
@@ -186,7 +186,7 @@ template.iterate(new RepeatCallback() {
|
||||
iteration (e.g. a set of chunks inside a step).</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="repeatListeners">
|
||||
<title>Listeners</title>
|
||||
|
||||
<para>Often it is useful to be able to receive additional callbacks for
|
||||
@@ -210,8 +210,7 @@ template.iterate(new RepeatCallback() {
|
||||
void onError(RepeatContext context, Throwable e);
|
||||
|
||||
void close(RepeatContext context);
|
||||
}
|
||||
</programlisting>The <methodname>open</methodname> and
|
||||
}</programlisting>The <methodname>open</methodname> and
|
||||
<methodname>close</methodname> callbacks come before and after the entire
|
||||
iteration. <methodname>before</methodname>, <methodname>after</methodname>
|
||||
and <methodname>onError</methodname> apply to the individual
|
||||
@@ -224,7 +223,7 @@ template.iterate(new RepeatCallback() {
|
||||
<methodname>close</methodname> are called in reverse order.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="repeatParallelProcessing">
|
||||
<title>Parallel Processing</title>
|
||||
|
||||
<para>Implementations of <classname>RepeatOperations</classname> are not
|
||||
@@ -234,12 +233,12 @@ template.iterate(new RepeatCallback() {
|
||||
<classname>TaskExecutorRepeatTemplate</classname>, which uses the Spring
|
||||
<classname>TaskExecutor</classname> strategy to run the
|
||||
<classname>RepeatCallback</classname>. The default is to use a
|
||||
<classname>SynchronousTaskExecutor</classname>, which has the effect of executing the whole
|
||||
iteration in the same thread (the same as a normal
|
||||
<classname>SynchronousTaskExecutor</classname>, which has the effect of
|
||||
executing the whole iteration in the same thread (the same as a normal
|
||||
<classname>RepeatTemplate</classname>).</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="declarativeIteration">
|
||||
<title>Declarative Iteration</title>
|
||||
|
||||
<para>Sometimes there is some business processing that you know you want
|
||||
@@ -266,9 +265,7 @@ template.iterate(new RepeatCallback() {
|
||||
advice-ref="retryAdvice" order="-1"/>
|
||||
</aop:config>
|
||||
|
||||
<bean id="retryAdvice"
|
||||
class="org.springframework.batch.repeat.interceptor.RepeatOperationsInterceptor"/>
|
||||
</programlisting>
|
||||
<bean id="retryAdvice" class="org.spr...RepeatOperationsInterceptor"/></programlisting>
|
||||
|
||||
<para>The example above uses a default
|
||||
<classname>RepeatTemplate</classname> inside the interceptor. To change
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<chapter id="retry">
|
||||
<title>Retry</title>
|
||||
|
||||
<section>
|
||||
<section id="retryTemplate">
|
||||
<title>RetryTemplate</title>
|
||||
|
||||
<para>To make processing more robust and less prone to failure, sometimes
|
||||
@@ -68,7 +68,7 @@ Foo result = template.execute(new RetryCallback<Foo>() {
|
||||
to the user. If that call fails then it is retried until a timeout is
|
||||
reached.</para>
|
||||
|
||||
<section>
|
||||
<section id="retryContext">
|
||||
<title>RetryContext</title>
|
||||
|
||||
<para>The method parameter for the <classname>RetryCallback</classname>
|
||||
@@ -82,7 +82,7 @@ Foo result = template.execute(new RetryCallback<Foo>() {
|
||||
between calls to <methodname>execute</methodname>.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="recoveryCallback">
|
||||
<title>RecoveryCallback</title>
|
||||
|
||||
<para>When a retry is exhausted the
|
||||
@@ -104,7 +104,7 @@ Foo result = template.execute(new RetryCallback<Foo>() {
|
||||
alternate processing through the recovery callback.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="statelessRetry">
|
||||
<title>Stateless Retry</title>
|
||||
|
||||
<para>In the simplest case, a retry is just a while loop: the
|
||||
@@ -120,7 +120,7 @@ Foo result = template.execute(new RetryCallback<Foo>() {
|
||||
when it failed.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="statefulRetry">
|
||||
<title>Stateful Retry</title>
|
||||
|
||||
<para>Where the failure has caused a transactional resource to become
|
||||
@@ -183,7 +183,7 @@ Foo result = template.execute(new RetryCallback<Foo>() {
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="retryPolicies">
|
||||
<title>Retry Policies</title>
|
||||
|
||||
<para>Inside a <classname>RetryTemplate</classname> the decision to retry
|
||||
@@ -245,7 +245,7 @@ template.execute(new RetryCallback<Foo>() {
|
||||
<para>There is also a more flexible implementation called
|
||||
<classname>ExceptionClassifierRetryPolicy</classname>, which allows the
|
||||
user to configure different retry behavior for an arbitrary set of
|
||||
excecption types though the <classname>ExceptionClassifier</classname>
|
||||
exception types though the <classname>ExceptionClassifier</classname>
|
||||
abstraction. The policy works by calling on the classifier to convert an
|
||||
exception into a delegate <classname>RetryPolicy</classname>, so for
|
||||
example, one exception type can be retried more times before failure than
|
||||
@@ -257,7 +257,7 @@ template.execute(new RetryCallback<Foo>() {
|
||||
retryable.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="backoffPolicies">
|
||||
<title>Backoff Policies</title>
|
||||
|
||||
<para>When retrying after a transient failure it often helps to wait a bit
|
||||
@@ -283,7 +283,7 @@ template.execute(new RetryCallback<Foo>() {
|
||||
<classname>ExponentialBackoffPolicy</classname>.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="retryListeners">
|
||||
<title>Listeners</title>
|
||||
|
||||
<para>Often it is useful to be able to receive additional callbacks for
|
||||
@@ -304,8 +304,7 @@ template.execute(new RetryCallback<Foo>() {
|
||||
void onError(RetryContext context, RetryCallback<T> callback, Throwable e);
|
||||
|
||||
void close(RetryContext context, RetryCallback<T> callback, Throwable e);
|
||||
}
|
||||
</programlisting>The <methodname>open</methodname> and
|
||||
}</programlisting>The <methodname>open</methodname> and
|
||||
<methodname>close</methodname> callbacks come before and after the entire
|
||||
retry in the simplest case and <methodname>onError</methodname> applies to
|
||||
the individual <classname>RetryCallback</classname> calls. The
|
||||
@@ -319,7 +318,7 @@ template.execute(new RetryCallback<Foo>() {
|
||||
<methodname>close</methodname> will be called in reverse order.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="declarativeRetry">
|
||||
<title>Declarative Retry</title>
|
||||
|
||||
<para>Sometimes there is some business processing that you know you want
|
||||
@@ -344,8 +343,7 @@ template.execute(new RetryCallback<Foo>() {
|
||||
</aop:config>
|
||||
|
||||
<bean id="retryAdvice"
|
||||
class="org.springframework.batch.retry.interceptor.RetryOperationsInterceptor"/>
|
||||
</programlisting>
|
||||
class="org.springframework.batch.retry.interceptor.RetryOperationsInterceptor"/></programlisting>
|
||||
|
||||
<para>The example above uses a default
|
||||
<classname>RetryTemplate</classname> inside the interceptor. To change the
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<para>Next we review the single-process options first, and then the
|
||||
multi-process options.</para>
|
||||
|
||||
<section>
|
||||
<section id="multithreadedStep">
|
||||
<title>Multi-threaded Step</title>
|
||||
|
||||
<para>The simplest way to start parallel processing is to add a
|
||||
@@ -47,11 +47,11 @@
|
||||
attribute of the <literal>tasklet</literal>:</para>
|
||||
|
||||
<programlisting><step id="loading">
|
||||
<tasklet reader="stagingReader"
|
||||
processor="stagingProcessor"
|
||||
writer="tradeWriter"
|
||||
commit-interval="1"
|
||||
task-executor="taskExecutor"/>
|
||||
<tasklet reader="stagingReader"
|
||||
processor="stagingProcessor"
|
||||
writer="tradeWriter"
|
||||
commit-interval="1"
|
||||
task-executor="taskExecutor"/>
|
||||
</step></programlisting>
|
||||
|
||||
<para>In this example the taskExecutor is a reference to another bean
|
||||
@@ -78,7 +78,7 @@
|
||||
track of items that have been processed in a database input table.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="scalabilityParallelSteps">
|
||||
<title>Parallel Steps</title>
|
||||
|
||||
<para>As long as the application logic that needs to be parallelized can
|
||||
@@ -89,19 +89,19 @@
|
||||
<literal>step3</literal>, you could configure a flow like this:</para>
|
||||
|
||||
<para><programlisting><job id="job1">
|
||||
<split id="split1" task-executor="taskExecutor" next="step4">
|
||||
<flow>
|
||||
<step id="step1" parent="s1" next="step2"/>
|
||||
<step id="step2" parent="s2"/>
|
||||
</flow>
|
||||
<flow>
|
||||
<step id="step3" parent="s3"/>
|
||||
</flow>
|
||||
</split>
|
||||
<step id="step4" parent="s4"/>
|
||||
<split id="split1" task-executor="taskExecutor" next="step4">
|
||||
<flow>
|
||||
<step id="step1" parent="s1" next="step2"/>
|
||||
<step id="step2" parent="s2"/>
|
||||
</flow>
|
||||
<flow>
|
||||
<step id="step3" parent="s3"/>
|
||||
</flow>
|
||||
</split>
|
||||
<step id="step4" parent="s4"/>
|
||||
</job>
|
||||
|
||||
<beans:bean id="taskExecutor" class="org.springframework.core.task.SimpleAsyncTaskExecutor"/></programlisting></para>
|
||||
<beans:bean id="taskExecutor" class="org.spr...SimpleAsyncTaskExecutor"/></programlisting></para>
|
||||
|
||||
<para>The configurable "task-executor" attribute is used to specify which
|
||||
TaskExecutor implementation should be used to execute the individual
|
||||
@@ -217,7 +217,7 @@
|
||||
<classname>org.springframework.batch.core.partition</classname>
|
||||
package).</para>
|
||||
|
||||
<section>
|
||||
<section id="partitionHandler">
|
||||
<title>PartitionHandler</title>
|
||||
|
||||
<para>The <classname>PartitionHandler</classname> is the component that
|
||||
@@ -247,10 +247,10 @@
|
||||
<classname>TaskExecutorPartitionHandler</classname>, and it can be
|
||||
configured like this:</para>
|
||||
|
||||
<para><programlisting><bean class="org.sfw..TaskExecutorPartitionHandler">
|
||||
<property name="taskExecutor" ref="taskExecutor"/>
|
||||
<property name="step" ref="step1" />
|
||||
<property name="gridSize" value="10" />
|
||||
<para><programlisting><bean class="org.spr...TaskExecutorPartitionHandler">
|
||||
<property name="taskExecutor" ref="taskExecutor"/>
|
||||
<property name="step" ref="step1" />
|
||||
<property name="gridSize" value="10" />
|
||||
</bean></programlisting></para>
|
||||
|
||||
<para>The <literal>gridSize</literal> determines the number of separate
|
||||
@@ -264,7 +264,7 @@
|
||||
replicating filesystems into content management systems.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="stepExecutionSplitter">
|
||||
<title>StepExecutionSplitter</title>
|
||||
|
||||
<para>The <classname>StepExecutionSplitter</classname> is responsible
|
||||
@@ -274,9 +274,9 @@
|
||||
principal method for this in the interface is</para>
|
||||
|
||||
<programlisting>public interface StepExecutionSplitter {
|
||||
...
|
||||
Set<StepExecution> split(StepExecution stepExecution, int gridSize)
|
||||
throws JobExecutionException;
|
||||
...
|
||||
Set<StepExecution> split(StepExecution stepExecution, int gridSize)
|
||||
throws JobExecutionException;
|
||||
}</programlisting>
|
||||
|
||||
<para>So an execution instance for the Master step is passed in, along
|
||||
@@ -295,7 +295,7 @@
|
||||
restarts). It has a single method:</para>
|
||||
|
||||
<programlisting>public interface Partitioner {
|
||||
Map<String, ExecutionContext> partition(int gridSize);
|
||||
Map<String, ExecutionContext> partition(int gridSize);
|
||||
}</programlisting>
|
||||
|
||||
<para>The return value from this method associates a unique name for
|
||||
@@ -322,7 +322,7 @@
|
||||
this convention.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="bindingInputDataToSteps">
|
||||
<title>Binding Input Data to Steps</title>
|
||||
|
||||
<para>It is very efficient for the steps that are executed by the
|
||||
@@ -374,10 +374,10 @@
|
||||
<para>Then the file name can be bound to a step using late binding to
|
||||
the execution context:</para>
|
||||
|
||||
<programlisting> <bean id="itemReader" scope="step"
|
||||
class="org.sfw...MultiResourceItemReader">
|
||||
<programlisting><bean id="itemReader" scope="step"
|
||||
class="org.spr...MultiResourceItemReader">
|
||||
<property name="resource" value="<emphasis role="bold">#{stepExecutionContext[fileName]}/*</emphasis>"/>
|
||||
</bean></programlisting>
|
||||
</bean></programlisting>
|
||||
</section>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE appendix PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
|
||||
<appendix>
|
||||
<appendix id="metaDataSchema">
|
||||
<title>Meta-Data Schema</title>
|
||||
|
||||
<section>
|
||||
<section id="metaDataSchemaOverview">
|
||||
<title>Overview</title>
|
||||
|
||||
<para>The Spring Batch Meta-Data tables very closely match the Domain
|
||||
@@ -37,7 +37,7 @@
|
||||
</imageobject>
|
||||
</mediaobject>
|
||||
|
||||
<section>
|
||||
<section id="exampleDDLScripts">
|
||||
<title>Example DDL Scripts</title>
|
||||
|
||||
<para>The root of the Spring Batch Core JAR file contains example
|
||||
@@ -49,7 +49,7 @@
|
||||
short name of the target database platform.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="metaDataVersion">
|
||||
<title>Version</title>
|
||||
|
||||
<para>Many of the database tables discussed in this appendix contain a
|
||||
@@ -64,7 +64,7 @@
|
||||
different machines, they are all using the same database tables.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="metaDataIdentity">
|
||||
<title>Identity</title>
|
||||
|
||||
<para>BATCH_JOB_INSTANCE, BATCH_JOB_EXECUTION, and BATCH_STEP_EXECUTION
|
||||
@@ -83,7 +83,7 @@ CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ;
|
||||
CREATE SEQUENCE BATCH_JOB_SEQ;</programlisting>
|
||||
|
||||
<para>Many database vendors don't support sequences. In these cases,
|
||||
work arounds are used, such as the following for mySQL:</para>
|
||||
work-arounds are used, such as the following for mySQL:</para>
|
||||
|
||||
<programlisting>CREATE TABLE BATCH_STEP_EXECUTION_SEQ (ID BIGINT NOT NULL) type=MYISAM;
|
||||
INSERT INTO BATCH_STEP_EXECUTION_SEQ values(0);
|
||||
@@ -99,7 +99,7 @@ INSERT INTO BATCH_JOB_SEQ values(0);</programlisting>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="metaDataBatchJobInstance">
|
||||
<title>BATCH_JOB_INSTANCE</title>
|
||||
|
||||
<para>The BATCH_JOB_INSTANCE table holds all information relevant to a
|
||||
@@ -145,7 +145,7 @@ INSERT INTO BATCH_JOB_SEQ values(0);</programlisting>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="metaDataBatchJobParams">
|
||||
<title>BATCH_JOB_PARAMS</title>
|
||||
|
||||
<para>The BATCH_JOB_PARAMS table holds all information relevant to the
|
||||
@@ -211,7 +211,7 @@ INSERT INTO BATCH_JOB_SEQ values(0);</programlisting>
|
||||
generated key, without causing any issues to the framework itself.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="metaDataBatchJobExecution">
|
||||
<title>BATCH_JOB_EXECUTION</title>
|
||||
|
||||
<para>The BATCH_JOB_EXECUTION table holds all information relevant to the
|
||||
@@ -298,7 +298,7 @@ INSERT INTO BATCH_JOB_SEQ values(0);</programlisting>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="metaDataBatchStepExecution">
|
||||
<title>BATCH_STEP_EXECUTION</title>
|
||||
|
||||
<para>The BATCH_STEP_EXECUTION table holds all information relevant to the
|
||||
@@ -437,7 +437,7 @@ INSERT INTO BATCH_JOB_SEQ values(0);</programlisting>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="metaDataBatchJobExecutionContext">
|
||||
<title>BATCH_JOB_EXECUTION_CONTEXT</title>
|
||||
|
||||
<para>The BATCH_JOB_EXECUTION_CONTEXT table holds all information relevant
|
||||
@@ -478,7 +478,7 @@ INSERT INTO BATCH_JOB_SEQ values(0);</programlisting>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="metaDataBatchStepExecutionContext">
|
||||
<title>BATCH_STEP_EXECUTION_CONTEXT</title>
|
||||
|
||||
<para>The BATCH_STEP_EXECUTION_CONTEXT table holds all information
|
||||
@@ -519,7 +519,7 @@ INSERT INTO BATCH_JOB_SEQ values(0);</programlisting>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="metaDataArchiving">
|
||||
<title>Archiving</title>
|
||||
|
||||
<para>Because there are entries in multiple tables every time a batch job
|
||||
@@ -551,7 +551,7 @@ INSERT INTO BATCH_JOB_SEQ values(0);</programlisting>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="recommendationsForIndexingMetaDataTables">
|
||||
<title>Recommendations for Indexing Meta Data Tables</title>
|
||||
|
||||
<para>Spring Batch provides DDL samples for the meta-data tables in the
|
||||
@@ -565,8 +565,8 @@ INSERT INTO BATCH_JOB_SEQ values(0);</programlisting>
|
||||
projects can make up their own minds about indexing.</para>
|
||||
|
||||
<table>
|
||||
<title>Where clauses in SQL statements (exluding primary keys) and their
|
||||
approximate frequency of use.</title>
|
||||
<title>Where clauses in SQL statements (excluding primary keys) and
|
||||
their approximate frequency of use.</title>
|
||||
|
||||
<tgroup cols="3">
|
||||
<tbody>
|
||||
|
||||
@@ -339,7 +339,7 @@ itemWriter.write(items);</programlisting>
|
||||
</section>
|
||||
|
||||
<section id="stepRestartExample">
|
||||
<title>Step restart configuration example</title>
|
||||
<title>Step Restart Configuration Example</title>
|
||||
|
||||
<programlisting><job id="footballJob" restartable="true">
|
||||
<step id="playerload" next="gameLoad">
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
focus on. The spring-batch-test project includes classes that will help
|
||||
facilitate this end-to-end test approach.</para>
|
||||
|
||||
<section>
|
||||
<section id="creatingUnitTestClass">
|
||||
<title>Creating a Unit Test Class</title>
|
||||
|
||||
<para>In order for the unit test to run a batch job, the framework must
|
||||
@@ -31,16 +31,14 @@
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<programlisting>
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(locations = { "/simple-job-launcher-context.xml", "/jobs/skipSampleJob.xml" })
|
||||
public class SkipSampleFunctionalTests extends AbstractJobTests { ... }
|
||||
|
||||
</programlisting>
|
||||
<programlisting>@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(locations = { "/simple-job-launcher-context.xml",
|
||||
"/jobs/skipSampleJob.xml" })
|
||||
public class SkipSampleFunctionalTests extends AbstractJobTests { ... }</programlisting>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>End To End Testing Batch Jobs</title>
|
||||
<section id="endToEndTesting">
|
||||
<title>End-To-End Testing of Batch Jobs</title>
|
||||
|
||||
<para>'End To End' testing can be defined as testing the complete run of a
|
||||
batch job from beginning to end. This allows for a test that sets up a
|
||||
@@ -61,36 +59,35 @@
|
||||
case below, the test verifies that the <classname>Job</classname> ended
|
||||
with status "COMPLETED".</para>
|
||||
|
||||
<programlisting>
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(locations = { "/simple-job-launcher-context.xml", "/jobs/skipSampleJob.xml" })
|
||||
public class SkipSampleFunctionalTests extends AbstractJobTests {
|
||||
<programlisting>@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(locations = { "/simple-job-launcher-context.xml",
|
||||
"/jobs/skipSampleJob.xml" })
|
||||
public class SkipSampleFunctionalTests extends AbstractJobTests {
|
||||
|
||||
private SimpleJdbcTemplate simpleJdbcTemplate;
|
||||
private SimpleJdbcTemplate simpleJdbcTemplate;
|
||||
|
||||
@Autowired
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
|
||||
}
|
||||
@Autowired
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Test
|
||||
public void testJob() throws Exception {
|
||||
simpleJdbcTemplate.update("delete from CUSTOMER");
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
simpleJdbcTemplate.update("insert into CUSTOMER values (?, 0, ?, 100000)", i, "customer" + i);
|
||||
}
|
||||
@Transactional
|
||||
@Test
|
||||
public void testJob() throws Exception {
|
||||
simpleJdbcTemplate.update("delete from CUSTOMER");
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
simpleJdbcTemplate.update("insert into CUSTOMER values (?, 0, ?, 100000)",
|
||||
i, "customer" + i);
|
||||
}
|
||||
|
||||
JobExecution jobExecution = <emphasis role="bold">this.launchJob()</emphasis>;
|
||||
JobExecution jobExecution = <emphasis role="bold">this.launchJob()</emphasis>;
|
||||
|
||||
Assert.assertEquals("COMPLETED", jobExecution.getExitStatus());
|
||||
}
|
||||
}
|
||||
|
||||
</programlisting>
|
||||
Assert.assertEquals("COMPLETED", jobExecution.getExitStatus());
|
||||
}
|
||||
}</programlisting>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="testingIndividualSteps">
|
||||
<title>Testing Individual Steps</title>
|
||||
|
||||
<para>For complex batch jobs, test cases in the end-to-end testing
|
||||
@@ -102,13 +99,10 @@
|
||||
targeted tests by allowing the test to set up data for just that step and
|
||||
to validate its results directly.</para>
|
||||
|
||||
<programlisting>
|
||||
JobExecution jobExecution = this.launchStep("loadFileStep");
|
||||
|
||||
</programlisting>
|
||||
<programlisting>JobExecution jobExecution = this.launchStep("loadFileStep");</programlisting>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="validatingOutputFiles">
|
||||
<title>Validating Output Files</title>
|
||||
|
||||
<para>When a batch job writes to the database, it is easy to query the
|
||||
@@ -123,35 +117,30 @@
|
||||
file with the expected output and to compare it to the actual
|
||||
result:</para>
|
||||
|
||||
<programlisting>
|
||||
private static final String EXPECTED_FILE = "src/main/resources/data/iosample/input/multiLine.txt";
|
||||
private static final String OUTPUT_FILE = "target/test-outputs/multiLineOutput.txt";
|
||||
<programlisting>private static final String EXPECTED_FILE = "src/main/resources/data/input.txt";
|
||||
private static final String OUTPUT_FILE = "target/test-outputs/output.txt";
|
||||
|
||||
AssertFile.assertFileEquals(new FileSystemResource(EXPECTED_FILE), new FileSystemResource(OUTPUT_FILE));
|
||||
|
||||
</programlisting>
|
||||
AssertFile.assertFileEquals(new FileSystemResource(EXPECTED_FILE),
|
||||
new FileSystemResource(OUTPUT_FILE));</programlisting>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="mockingDomainObjects">
|
||||
<title>Mocking Domain Objects</title>
|
||||
|
||||
<para>Another common issue encountered while writing unit and integration
|
||||
tests for Spring Batch components is how to mock domain objects. A good
|
||||
example is a <classname>StepExecutionListener</classname>, as illustrated
|
||||
below: </para>
|
||||
below:</para>
|
||||
|
||||
<programlisting>
|
||||
public class NoWorkFoundStepExecutionListener extends StepExecutionListenerSupport {
|
||||
<programlisting>public class NoWorkFoundStepExecutionListener extends StepExecutionListenerSupport {
|
||||
|
||||
public ExitStatus afterStep(StepExecution stepExecution) {
|
||||
if (stepExecution.getReadCount() == 0) {
|
||||
throw new NoWorkFoundException("Step has not processed any items");
|
||||
}
|
||||
return stepExecution.getExitStatus();
|
||||
if (stepExecution.getReadCount() == 0) {
|
||||
throw new NoWorkFoundException("Step has not processed any items");
|
||||
}
|
||||
return stepExecution.getExitStatus();
|
||||
}
|
||||
}
|
||||
|
||||
</programlisting>
|
||||
}</programlisting>
|
||||
|
||||
<para>The above listener is provided by the framework and checks a
|
||||
<classname>StepExecution</classname> for an empty read count, thus
|
||||
@@ -160,26 +149,22 @@
|
||||
attempting to unit test classes that implement interfaces requiring Spring
|
||||
Batch domain objects. Consider the above listener's unit test:</para>
|
||||
|
||||
<programlisting>
|
||||
private NoWorkFoundStepExecutionListener tested = new NoWorkFoundStepExecutionListener();
|
||||
<programlisting>private NoWorkFoundStepExecutionListener tested = new NoWorkFoundStepExecutionListener();
|
||||
|
||||
@Test
|
||||
public void testAfterStep() {
|
||||
<emphasis role="bold">StepExecution stepExecution = new StepExecution("NoProcessingStep",
|
||||
new JobExecution(
|
||||
new JobInstance(1L, new JobParameters(), "NoProcessingJob")));</emphasis>
|
||||
@Test
|
||||
public void testAfterStep() {
|
||||
<emphasis role="bold">StepExecution stepExecution = new StepExecution("NoProcessingStep",
|
||||
new JobExecution(new JobInstance(1L, new JobParameters(), "NoProcessingJob")));</emphasis>
|
||||
|
||||
stepExecution.setReadCount(0);
|
||||
stepExecution.setReadCount(0);
|
||||
|
||||
try {
|
||||
tested.afterStep(stepExecution);
|
||||
fail();
|
||||
} catch (NoWorkFoundException e) {
|
||||
assertEquals("Step has not processed any items", e.getMessage());
|
||||
}
|
||||
try {
|
||||
tested.afterStep(stepExecution);
|
||||
fail();
|
||||
} catch (NoWorkFoundException e) {
|
||||
assertEquals("Step has not processed any items", e.getMessage());
|
||||
}
|
||||
|
||||
</programlisting>
|
||||
}</programlisting>
|
||||
|
||||
<para>Because the Spring Batch domain model follows good object orientated
|
||||
principles, the StepExecution requires a
|
||||
@@ -190,27 +175,24 @@
|
||||
model, it does make creating stub objects for unit testing verbose. To
|
||||
address this issue, the Spring Batch test module includes a factory for
|
||||
creating domain objects: <classname>MetaDataInstanceFactory</classname>.
|
||||
Given this factory, the unit test can be updated to be more concise:
|
||||
</para>
|
||||
Given this factory, the unit test can be updated to be more
|
||||
concise:</para>
|
||||
|
||||
<programlisting>
|
||||
private NoWorkFoundStepExecutionListener tested = new NoWorkFoundStepExecutionListener();
|
||||
<programlisting>private NoWorkFoundStepExecutionListener tested = new NoWorkFoundStepExecutionListener();
|
||||
|
||||
@Test
|
||||
public void testAfterStep() {
|
||||
<emphasis role="bold">StepExecution stepExecution = MetaDataInstanceFactory.createStepExecution();</emphasis>
|
||||
@Test
|
||||
public void testAfterStep() {
|
||||
<emphasis role="bold">StepExecution stepExecution = MetaDataInstanceFactory.createStepExecution();</emphasis>
|
||||
|
||||
stepExecution.setReadCount(0);
|
||||
stepExecution.setReadCount(0);
|
||||
|
||||
try {
|
||||
tested.afterStep(stepExecution);
|
||||
fail();
|
||||
} catch (NoWorkFoundException e) {
|
||||
assertEquals("Step has not processed any items", e.getMessage());
|
||||
}
|
||||
try {
|
||||
tested.afterStep(stepExecution);
|
||||
fail();
|
||||
} catch (NoWorkFoundException e) {
|
||||
assertEquals("Step has not processed any items", e.getMessage());
|
||||
}
|
||||
|
||||
</programlisting>
|
||||
}</programlisting>
|
||||
|
||||
<para>The above method for creating a simple
|
||||
<classname>StepExecution</classname> is just one convenience method
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
|
||||
<chapter id="whatsNew">
|
||||
<title>What's new in Spring Batch 2.0</title>
|
||||
<title>What's New in Spring Batch 2.0</title>
|
||||
|
||||
<para>The Spring Batch 2.0 release has six major themes:</para>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user