Update ref guide for new XML features
This commit is contained in:
@@ -4,8 +4,9 @@
|
||||
<chapter id="configureJob">
|
||||
<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>
|
||||
<para>In the <link linkend="domain">domain section</link> , the
|
||||
overall architecture design was discussed, using the following
|
||||
diagram as a guide:</para>
|
||||
|
||||
<mediaobject>
|
||||
<imageobject role="html">
|
||||
@@ -35,7 +36,7 @@
|
||||
<classname>Job</classname> </link> interface, however, the namespace
|
||||
abstracts away the differences in configuration. It has only three
|
||||
required dependencies: a name, <classname>JobRepository</classname> , and
|
||||
a list of <classname>Step</classname> s.</para>
|
||||
a list of <classname>Step</classname>s.</para>
|
||||
|
||||
<programlisting><job id="footballJob">
|
||||
<step id="playerload" parent="s1" next="gameLoad"/>
|
||||
@@ -43,9 +44,11 @@
|
||||
<step id="playerSummarization" parent="s3"/>
|
||||
</job></programlisting>
|
||||
|
||||
<para>The namespace defaults to referencing a repository with an id of
|
||||
'jobRepository', which is a sensible default. However, this can be
|
||||
overridden explicitly:</para>
|
||||
<para>The examples here use a parent bean definition to create the steps;
|
||||
see the section on <link linkend="configureStep">step configuration</link>
|
||||
for more options declaring specific step details inline. The XML namespace
|
||||
defaults to referencing a repository with an id of 'jobRepository', which
|
||||
is a sensible default. However, this can be overridden explicitly:</para>
|
||||
|
||||
<programlisting><job id="footballJob" <emphasis role="bold">job-repository="specialRepository"</emphasis>>
|
||||
<step id="playerload" parent="s1" next="gameLoad"/>
|
||||
@@ -53,6 +56,12 @@
|
||||
<step id="playerSummarization" parent="s3"/>
|
||||
</job></programlisting>
|
||||
|
||||
<para>In addition to steps a job configuration can contain other elements
|
||||
that help with parallelisation (<literal><split/></literal>),
|
||||
declarative flow control (<literal><decision/></literal>) and
|
||||
externalization of flow definitions
|
||||
(<literal><flow/></literal>).</para>
|
||||
|
||||
<section id="restartability">
|
||||
<title>Restartability</title>
|
||||
|
||||
@@ -194,31 +203,26 @@ catch (JobRestartException e) {
|
||||
for more detailed information.</para>
|
||||
</section>
|
||||
|
||||
<section id="jobFactoryAndStatefulComponentsInSteps">
|
||||
<title>JobFactory and Stateful Components in Steps</title>
|
||||
<section>
|
||||
<title>JobParametersValidator</title>
|
||||
|
||||
<para>Unlike many traditional Spring applications, many of the
|
||||
components of a batch application are stateful; the file readers and
|
||||
writers are obvious examples. The recommended way to deal with this is
|
||||
to create a fresh <classname>ApplicationContext</classname> for each job
|
||||
execution. If the <classname>Job</classname> is launched from the
|
||||
command line with <classname>CommandLineJobRunner</classname>, this is
|
||||
trivial. For more complex launching scenarios where jobs are executed in
|
||||
parallel or serially from the same process, some extra steps have to be
|
||||
taken to ensure that the <classname>ApplicationContext</classname> is
|
||||
refreshed. This is preferable to using prototype scope for the stateful
|
||||
beans because then they would not receive lifecycle callbacks from the
|
||||
container at the end of use. (e.g. through destroy-method in XML)</para>
|
||||
<para>A job declared in the XML namespace or using any subclass of
|
||||
AbstractJob can optionally declare a validator for the job parameters at
|
||||
runtime. This is useful when for instance you need to assert that a job
|
||||
is started with all its mandatory parameters. There is a
|
||||
DefaultJobParametersValidator that can be used to constrain combinations
|
||||
of simple mandatory and optional parameters, and for more complex
|
||||
constraints you can implement the interface yourself. The configuration
|
||||
of a validator is supported through the XML namespace through a child
|
||||
element of the job, e.g>:</para>
|
||||
|
||||
<para>The strategy provided by Spring Batch to deal with this scenario
|
||||
is the <classname>JobFactory</classname>, and the samples provide an
|
||||
example of a specialized implementation that can load an
|
||||
<classname>ApplicationContext</classname> and close it properly when the
|
||||
job is finished. A relevant examples is
|
||||
<classname>ClassPathXmlApplicationContextJobFactory</classname> and its
|
||||
use in the <code>adhoc-job-launcher-context.xml</code> and the
|
||||
<code>quartz-job-launcher-context.xml</code>, which can be found in the
|
||||
Samples project.</para>
|
||||
<programlisting><job id="job1" parent="baseJob3">
|
||||
<step id="step1" parent="standaloneStep"/>
|
||||
<validator ref="paremetersValidator"/>
|
||||
</job></programlisting>
|
||||
|
||||
<para>The validator can be specified as a reference (as above) or as a
|
||||
nested bean definition in the beans namespace.</para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -537,11 +541,13 @@ catch (JobRestartException e) {
|
||||
|
||||
<screen><prompt>bash$</prompt> java CommandLineJobRunner endOfDayJob.xml endOfDay schedule.date(date)=2008/01/01</screen>
|
||||
|
||||
<para>In most cases you would want to use a manifest to declare your
|
||||
main class in a jar, but for simplicity, the class was used directly.
|
||||
This example is using the same 'EndOfDay' example from <xref
|
||||
linkend="domain" />. The first argument is 'endOfDayJob.xml', which is
|
||||
the Spring <classname>ApplicationContext</classname> containing the
|
||||
<para>In most cases you would want to use a manifest to
|
||||
declare your main class in a jar, but for simplicity, the
|
||||
class was used directly. This example is using the same
|
||||
'EndOfDay' example from the <link linkend="domain">domain
|
||||
section</link>. 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(date)=2008/01/01'
|
||||
will be converted into <classname>JobParameters</classname>. An
|
||||
|
||||
@@ -54,13 +54,13 @@
|
||||
<para>Below is a code representation of the same concepts shown
|
||||
above:</para>
|
||||
|
||||
<programlisting><![CDATA[List items = new Arraylist();
|
||||
for(int i = 0; i < commitInterval; i++){
|
||||
<programlisting>List items = new Arraylist();
|
||||
for(int i = 0; i < commitInterval; i++){
|
||||
Object item = itemReader.read()
|
||||
Object processedItem = itemProcessor.process(item);
|
||||
items.add(processedItem);
|
||||
}
|
||||
itemWriter.write(items);]]></programlisting>
|
||||
itemWriter.write(items);</programlisting>
|
||||
|
||||
<section id="configuringAStep">
|
||||
<title>Configuring a Step</title>
|
||||
@@ -70,13 +70,13 @@ itemWriter.write(items);]]></programlisting>
|
||||
potentially contain many collaborators. In order to ease configuration,
|
||||
the Spring Batch namespace can be used:</para>
|
||||
|
||||
<programlisting><![CDATA[<job id="sampleJob" job-repository="jobRepository">
|
||||
<step id="step1">
|
||||
<tasklet transaction-manager="transactionManager">
|
||||
<chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
|
||||
<tasklet>
|
||||
</step>
|
||||
</job>]]></programlisting>
|
||||
<programlisting><job id="sampleJob" job-repository="jobRepository">
|
||||
<step id="step1">
|
||||
<tasklet transaction-manager="transactionManager">
|
||||
<chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
|
||||
<tasklet>
|
||||
</step>
|
||||
</job></programlisting>
|
||||
|
||||
<para>The configuration above represents the only required dependencies
|
||||
to create a item-oriented step:<itemizedlist>
|
||||
@@ -121,43 +121,6 @@ itemWriter.write(items);]]></programlisting>
|
||||
writer.</para>
|
||||
</section>
|
||||
|
||||
<section id="standaloneStep">
|
||||
<title>Referencing a Standalone Step</title>
|
||||
|
||||
<para>While steps must exist within a Job to define the flow, it can
|
||||
sometimes be useful to reference a 'standalone' Step. For example, if a
|
||||
Step is used by multiple jobs it can be useful to declare it once and
|
||||
reference it from multiple jobs. This can be achieved with the 'parent'
|
||||
attribute:</para>
|
||||
|
||||
<programlisting><job id="sampleJob" job-repository="jobRepository">
|
||||
<step id="step1" <emphasis role="bold">parent="standaloneStep"</emphasis> />
|
||||
</job>
|
||||
|
||||
<step id="standaloneStep">
|
||||
<tasklet job-repository="jobRepository" transaction-manager="transactionManager">
|
||||
<chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
|
||||
</tasklet>
|
||||
</step></programlisting>
|
||||
|
||||
<para>It should be noted that the id attribute is still required on the
|
||||
step within the job element. This is for two reasons:</para>
|
||||
|
||||
<orderedlist>
|
||||
<listitem>
|
||||
<para>The id will be used as the step name when persisting the
|
||||
StepExecution. If the same standalone step is referenced in more
|
||||
than one step in the job, an error will occur.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>When creating job flows, as described later in this chapter,
|
||||
the next attribute should be referring to the step in the flow, not
|
||||
the standalone step.</para>
|
||||
</listitem>
|
||||
</orderedlist>
|
||||
</section>
|
||||
|
||||
<section id="InheritingFromParentStep">
|
||||
<title>Inheriting from a Parent Step</title>
|
||||
|
||||
@@ -175,17 +138,32 @@ itemWriter.write(items);]]></programlisting>
|
||||
allowStartIfComplete=true. Additionally, the commitInterval will be '5'
|
||||
since it is overridden by the "concreteStep1":</para>
|
||||
|
||||
<programlisting><![CDATA[<step id="parentStep">
|
||||
<tasklet allow-start-if-complete="true">
|
||||
<chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
|
||||
</tasklet>
|
||||
</step>
|
||||
<programlisting><step id="parentStep">
|
||||
<tasklet allow-start-if-complete="true">
|
||||
<chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
|
||||
</tasklet>
|
||||
</step>
|
||||
|
||||
<step id="concreteStep1" parent="parentStep">
|
||||
<tasklet start-limit="5">
|
||||
<chunk processor="itemProcessor" commit-interval="5"/>
|
||||
</tasklet>
|
||||
</step>]]></programlisting>
|
||||
<step id="concreteStep1" parent="parentStep">
|
||||
<tasklet start-limit="5">
|
||||
<chunk processor="itemProcessor" commit-interval="5"/>
|
||||
</tasklet>
|
||||
</step></programlisting>
|
||||
|
||||
<para>The id attribute is still required on the step within the job
|
||||
element. This is for two reasons:<orderedlist>
|
||||
<listitem>
|
||||
<para>The id will be used as the step name when persisting the
|
||||
StepExecution. If the same standalone step is referenced in more
|
||||
than one step in the job, an error will occur.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>When creating job flows, as described later in this chapter,
|
||||
the next attribute should be referring to the step in the flow,
|
||||
not the standalone step.</para>
|
||||
</listitem>
|
||||
</orderedlist></para>
|
||||
|
||||
<section id="abstractStep">
|
||||
<title>Abstract Step</title>
|
||||
@@ -205,17 +183,17 @@ itemWriter.write(items);]]></programlisting>
|
||||
be abstract. The <classname>Step</classname> "concreteStep2" will have
|
||||
'itemReader', 'itemWriter', and commitInterval=10.</para>
|
||||
|
||||
<programlisting><![CDATA[<step id="abstractParentStep" abstract="true">
|
||||
<tasklet>
|
||||
<chunk commit-interval="10"/>
|
||||
</tasklet>
|
||||
</step>
|
||||
<programlisting><step id="abstractParentStep" abstract="true">
|
||||
<tasklet>
|
||||
<chunk commit-interval="10"/>
|
||||
</tasklet>
|
||||
</step>
|
||||
|
||||
<step id="concreteStep2" parent="abstractParentStep">
|
||||
<tasklet>
|
||||
<chunk reader="itemReader" writer="itemWriter"/>
|
||||
</tasklet>
|
||||
</step>]]></programlisting>
|
||||
<step id="concreteStep2" parent="abstractParentStep">
|
||||
<tasklet>
|
||||
<chunk reader="itemReader" writer="itemWriter"/>
|
||||
</tasklet>
|
||||
</step></programlisting>
|
||||
</section>
|
||||
|
||||
<section id="mergingListsOnStep">
|
||||
@@ -236,20 +214,20 @@ itemWriter.write(items);]]></programlisting>
|
||||
<classname>listenerOne</classname> and
|
||||
<classname>listenerTwo</classname>:</para>
|
||||
|
||||
<programlisting><![CDATA[<step id="listenersParentStep" abstract="true">
|
||||
<listeners>
|
||||
<listener ref="listenerOne"/>
|
||||
<listeners>
|
||||
</step>
|
||||
<programlisting><step id="listenersParentStep" abstract="true">
|
||||
<listeners>
|
||||
<listener ref="listenerOne"/>
|
||||
<listeners>
|
||||
</step>
|
||||
|
||||
<step id="concreteStep3" parent="listenersParentStep">
|
||||
<tasklet>
|
||||
<chunk reader="itemReader" writer="itemWriter" commit-interval="5"/>
|
||||
<listeners merge="true">
|
||||
<listener ref="listenerTwo"/>
|
||||
<listeners>
|
||||
</tasklet>
|
||||
</step>]]></programlisting>
|
||||
<step id="concreteStep3" parent="listenersParentStep">
|
||||
<tasklet>
|
||||
<chunk reader="itemReader" writer="itemWriter" commit-interval="5"/>
|
||||
<listeners merge="true">
|
||||
<listener ref="listenerTwo"/>
|
||||
<listeners>
|
||||
</tasklet>
|
||||
</step></programlisting>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -341,26 +319,26 @@ itemWriter.write(items);]]></programlisting>
|
||||
<section id="stepRestartExample">
|
||||
<title>Step Restart Configuration Example</title>
|
||||
|
||||
<programlisting><![CDATA[<job id="footballJob" restartable="true">
|
||||
<step id="playerload" next="gameLoad">
|
||||
<tasklet>
|
||||
<chunk reader="playerFileItemReader" writer="playerWriter"
|
||||
commit-interval="10" />
|
||||
</tasklet>
|
||||
</step>
|
||||
<step id="gameLoad" next="playerSummarization">
|
||||
<tasklet allow-start-if-complete="true">
|
||||
<chunk reader="gameFileItemReader" writer="gameWriter"
|
||||
commit-interval="10"/>
|
||||
</tasklet>
|
||||
</step>
|
||||
<step id="playerSummarization">
|
||||
<tasklet start-limit="3">
|
||||
<chunk reader="playerSummarizationSource" writer="summaryWriter"
|
||||
commit-interval="10"/>
|
||||
</tasklet>
|
||||
</step>
|
||||
</job>]]></programlisting>
|
||||
<programlisting><job id="footballJob" restartable="true">
|
||||
<step id="playerload" next="gameLoad">
|
||||
<tasklet>
|
||||
<chunk reader="playerFileItemReader" writer="playerWriter"
|
||||
commit-interval="10" />
|
||||
</tasklet>
|
||||
</step>
|
||||
<step id="gameLoad" next="playerSummarization">
|
||||
<tasklet allow-start-if-complete="true">
|
||||
<chunk reader="gameFileItemReader" writer="gameWriter"
|
||||
commit-interval="10"/>
|
||||
</tasklet>
|
||||
</step>
|
||||
<step id="playerSummarization">
|
||||
<tasklet start-limit="3">
|
||||
<chunk reader="playerSummarizationSource" writer="summaryWriter"
|
||||
commit-interval="10"/>
|
||||
</tasklet>
|
||||
</step>
|
||||
</job></programlisting>
|
||||
|
||||
<para>The above example configuration is for a job that loads in
|
||||
information about football games and summarizes them. It contains
|
||||
@@ -570,14 +548,14 @@ itemWriter.write(items);]]></programlisting>
|
||||
the <classname>Step</classname> can be configured with a list of
|
||||
exceptions that should not cause rollback.</para>
|
||||
|
||||
<programlisting><![CDATA[<step id="step1">
|
||||
<tasklet>
|
||||
<chunk reader="itemReader" writer="itemWriter" commit-interval="2"/>
|
||||
<no-rollback-exception-classes>
|
||||
<include class="org.springframework.batch.item.validator.ValidationException"/>
|
||||
</no-rollback-exception-classes>
|
||||
</tasklet>
|
||||
</step>]]></programlisting>
|
||||
<programlisting><step id="step1">
|
||||
<tasklet>
|
||||
<chunk reader="itemReader" writer="itemWriter" commit-interval="2"/>
|
||||
<no-rollback-exception-classes>
|
||||
<include class="org.springframework.batch.item.validator.ValidationException"/>
|
||||
</no-rollback-exception-classes>
|
||||
</tasklet>
|
||||
</step></programlisting>
|
||||
|
||||
<section id="transactionalReaders">
|
||||
<title>Transactional Readers</title>
|
||||
@@ -609,14 +587,14 @@ itemWriter.write(items);]]></programlisting>
|
||||
transaction attributes can be found in the spring core
|
||||
documentation.</para>
|
||||
|
||||
<programlisting><![CDATA[<step id="step1">
|
||||
<tasklet>
|
||||
<chunk reader="itemReader" writer="itemWriter" commit-interval="2"/>
|
||||
<transaction-attributes isolation="DEFAULT"
|
||||
<programlisting><step id="step1">
|
||||
<tasklet>
|
||||
<chunk reader="itemReader" writer="itemWriter" commit-interval="2"/>
|
||||
<transaction-attributes isolation="DEFAULT"
|
||||
propagation="REQUIRED"
|
||||
timeout="30"/>
|
||||
</tasklet>
|
||||
</step>]]></programlisting>
|
||||
timeout="30"/>
|
||||
</tasklet>
|
||||
</step></programlisting>
|
||||
</section>
|
||||
|
||||
<section id="registeringItemStreams">
|
||||
@@ -689,14 +667,14 @@ itemWriter.write(items);]]></programlisting>
|
||||
interface (or an extension thereof) can be applied to a step via the
|
||||
listeners element:</para>
|
||||
|
||||
<programlisting><![CDATA[<step id="step1">
|
||||
<tasklet>
|
||||
<chunk reader="reader" writer="writer" commit-interval="10"/>
|
||||
<listeners>
|
||||
<listener ref="stepListener"/>
|
||||
</listeners>
|
||||
</tasklet>
|
||||
</step>]]></programlisting>
|
||||
<programlisting><step id="step1">
|
||||
<tasklet>
|
||||
<chunk reader="reader" writer="writer" commit-interval="10"/>
|
||||
<listeners>
|
||||
<listener ref="stepListener"/>
|
||||
</listeners>
|
||||
</tasklet>
|
||||
</step></programlisting>
|
||||
|
||||
<para>An <classname>ItemReader</classname>,
|
||||
<classname>ItemWriter</classname> or
|
||||
@@ -728,13 +706,13 @@ itemWriter.write(items);]]></programlisting>
|
||||
for notification before a <classname>Step</classname> is started and
|
||||
after it has ends, whether it ended normally or failed:</para>
|
||||
|
||||
<programlisting><![CDATA[public interface StepExecutionListener extends StepListener {
|
||||
<programlisting>public interface StepExecutionListener extends StepListener {
|
||||
|
||||
void beforeStep(StepExecution stepExecution);
|
||||
|
||||
ExitStatus afterStep(StepExecution stepExecution);
|
||||
|
||||
}]]></programlisting>
|
||||
}</programlisting>
|
||||
|
||||
<para><classname>ExitStatus</classname> is the return type of
|
||||
<methodname>afterStep</methodname> in order to allow listeners the
|
||||
@@ -763,13 +741,13 @@ itemWriter.write(items);]]></programlisting>
|
||||
useful to perform logic before a chunk begins processing or after a
|
||||
chunk has completed:</para>
|
||||
|
||||
<programlisting><![CDATA[public interface ChunkListener extends StepListener {
|
||||
<programlisting>public interface ChunkListener extends StepListener {
|
||||
|
||||
void beforeChunk();
|
||||
|
||||
void afterChunk();
|
||||
|
||||
}]]></programlisting>
|
||||
}</programlisting>
|
||||
|
||||
<para>The <methodname>beforeChunk</methodname> method is called after
|
||||
the transaction is started, but before <methodname>read</methodname>
|
||||
@@ -798,7 +776,7 @@ itemWriter.write(items);]]></programlisting>
|
||||
<para>When discussing skip logic above, it was mentioned that it may
|
||||
be beneficial to log out skipped records, so that they can be deal
|
||||
with later. In the case of read errors, this can be done with an
|
||||
<classname>ItemReaderListener:</classname><programlisting><![CDATA[public interface ItemReadListener<T> extends StepListener {
|
||||
<classname>ItemReaderListener:</classname><programlisting>public interface ItemReadListener<T> extends StepListener {
|
||||
|
||||
void beforeRead();
|
||||
|
||||
@@ -806,7 +784,7 @@ itemWriter.write(items);]]></programlisting>
|
||||
|
||||
void onReadError(Exception ex);
|
||||
|
||||
}]]></programlisting></para>
|
||||
}</programlisting></para>
|
||||
|
||||
<para>The <methodname>beforeRead</methodname> method will be called
|
||||
before each call to <methodname>read</methodname> on the
|
||||
@@ -841,7 +819,7 @@ itemWriter.write(items);]]></programlisting>
|
||||
<para>Just as with the <classname>ItemReadListener</classname>, the
|
||||
processing of an item can be 'listened' to:</para>
|
||||
|
||||
<programlisting><![CDATA[public interface ItemProcessListener<T, S> extends StepListener {
|
||||
<programlisting>public interface ItemProcessListener<T, S> extends StepListener {
|
||||
|
||||
void beforeProcess(T item);
|
||||
|
||||
@@ -849,7 +827,7 @@ itemWriter.write(items);]]></programlisting>
|
||||
|
||||
void onProcessError(T item, Exception e);
|
||||
|
||||
}]]></programlisting>
|
||||
}</programlisting>
|
||||
|
||||
<para>The <methodname>beforeProcess</methodname> method will be called
|
||||
before <methodname>process</methodname> on the
|
||||
@@ -884,15 +862,15 @@ itemWriter.write(items);]]></programlisting>
|
||||
<para>The writing of an item can be 'listened' to with the
|
||||
<classname>ItemWriteListener</classname>:</para>
|
||||
|
||||
<programlisting><![CDATA[ public interface ItemWriteListener<S> extends StepListener {
|
||||
<programlisting> public interface ItemWriteListener<S> extends StepListener {
|
||||
|
||||
void beforeWrite(List<? extends S> items);
|
||||
void beforeWrite(List<? extends S> items);
|
||||
|
||||
void afterWrite(List<? extends S> items);
|
||||
void afterWrite(List<? extends S> items);
|
||||
|
||||
void onWriteError(Exception exception, List<? extends S> items);
|
||||
void onWriteError(Exception exception, List<? extends S> items);
|
||||
|
||||
}]]></programlisting>
|
||||
}</programlisting>
|
||||
|
||||
<para>The <methodname>beforeWrite</methodname> method will be called
|
||||
before <methodname>write</methodname> on the
|
||||
@@ -932,8 +910,8 @@ itemWriter.write(items);]]></programlisting>
|
||||
this reason, there is a separate interface for tracking skipped
|
||||
items:</para>
|
||||
|
||||
<programlisting><![CDATA[
|
||||
public interface SkipListener<T,S> extends StepListener {
|
||||
<programlisting>
|
||||
public interface SkipListener<T,S> extends StepListener {
|
||||
|
||||
void onSkipInRead(Throwable t);
|
||||
|
||||
@@ -941,7 +919,7 @@ itemWriter.write(items);]]></programlisting>
|
||||
|
||||
void onSkipInWrite(S item, Throwable t);
|
||||
|
||||
}]]></programlisting>
|
||||
}</programlisting>
|
||||
|
||||
<para><methodname>onSkipInRead</methodname> will be called whenever an
|
||||
item is skipped while reading. It should be noted that rollbacks may
|
||||
@@ -1042,12 +1020,12 @@ itemWriter.write(items);]]></programlisting>
|
||||
this class without having to write an adapter for the
|
||||
<classname>Tasklet</classname> interface:</para>
|
||||
|
||||
<programlisting><![CDATA[<bean id="myTasklet" class="org.springframework.batch.core.step.tasklet.TaskletAdapter">
|
||||
<property name="targetObject">
|
||||
<bean class="org.mycompany.FooDao">
|
||||
</property>
|
||||
<property name="targetMethod" value="updateFoo" />
|
||||
</bean>]]></programlisting>
|
||||
<programlisting><bean id="myTasklet" class="org.springframework.batch.core.step.tasklet.TaskletAdapter">
|
||||
<property name="targetObject">
|
||||
<bean class="org.mycompany.FooDao">
|
||||
</property>
|
||||
<property name="targetMethod" value="updateFoo" />
|
||||
</bean></programlisting>
|
||||
</section>
|
||||
|
||||
<section id="exampleTaskletImplementation">
|
||||
@@ -1062,7 +1040,7 @@ itemWriter.write(items);]]></programlisting>
|
||||
project, is a <classname>Tasklet</classname> implementation with just
|
||||
such a responsibility:</para>
|
||||
|
||||
<programlisting><![CDATA[public class FileDeletingTasklet implements Tasklet, InitializingBean {
|
||||
<programlisting>public class FileDeletingTasklet implements Tasklet, InitializingBean {
|
||||
|
||||
private Resource directory;
|
||||
|
||||
@@ -1072,7 +1050,7 @@ itemWriter.write(items);]]></programlisting>
|
||||
Assert.state(dir.isDirectory());
|
||||
|
||||
File[] files = dir.listFiles();
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
boolean deleted = files[i].delete();
|
||||
if (!deleted) {
|
||||
throw new UnexpectedJobExecutionException("Could not delete file " +
|
||||
@@ -1089,7 +1067,7 @@ itemWriter.write(items);]]></programlisting>
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(directory, "directory must be set");
|
||||
}
|
||||
}]]></programlisting>
|
||||
}</programlisting>
|
||||
|
||||
<para>The above <classname>Tasklet</classname> implementation will
|
||||
delete all files within a given directory. It should be noted that the
|
||||
@@ -1097,21 +1075,21 @@ itemWriter.write(items);]]></programlisting>
|
||||
that is left is to reference the <classname>Tasklet</classname> from the
|
||||
<classname>Step</classname>:</para>
|
||||
|
||||
<programlisting><![CDATA[<job id="taskletJob">
|
||||
<step id="deleteFilesInDir">
|
||||
<tasklet ref="fileDeletingTasklet"/>
|
||||
</step>
|
||||
</job>
|
||||
<programlisting><job id="taskletJob">
|
||||
<step id="deleteFilesInDir">
|
||||
<tasklet ref="fileDeletingTasklet"/>
|
||||
</step>
|
||||
</job>
|
||||
|
||||
<beans:bean id="fileDeletingTasklet"
|
||||
class="org.springframework.batch.sample.tasklet.FileDeletingTasklet">
|
||||
<beans:property name="directoryResource">
|
||||
<beans:bean id="directory"
|
||||
class="org.springframework.core.io.FileSystemResource">
|
||||
<beans:constructor-arg value="target/test-outputs/test-dir" />
|
||||
</beans:bean>
|
||||
</beans:property>
|
||||
</beans:bean>]]></programlisting>
|
||||
<beans:bean id="fileDeletingTasklet"
|
||||
class="org.springframework.batch.sample.tasklet.FileDeletingTasklet">
|
||||
<beans:property name="directoryResource">
|
||||
<beans:bean id="directory"
|
||||
class="org.springframework.core.io.FileSystemResource">
|
||||
<beans:constructor-arg value="target/test-outputs/test-dir" />
|
||||
</beans:bean>
|
||||
</beans:property>
|
||||
</beans:bean></programlisting>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -1148,15 +1126,15 @@ itemWriter.write(items);]]></programlisting>
|
||||
<para>This can be achieved using the 'next' attribute of the step
|
||||
element:</para>
|
||||
|
||||
<para><programlisting><![CDATA[<job id="job">
|
||||
<step id="stepA" parent="s1" next="stepB" />
|
||||
<step id="stepB" parent="s2" next="stepC"/>
|
||||
<step id="stepC" parent="s3" />
|
||||
</job>]]></programlisting>In the scenario above, 'step A' will execute first
|
||||
because it is the first <classname>Step</classname> listed. If 'step A'
|
||||
completes normally, then 'step B' will execute, and so on. However, if
|
||||
'step A' fails, then the entire <classname>Job</classname> will fail and
|
||||
'step B' will not execute.</para>
|
||||
<para><programlisting><job id="job">
|
||||
<step id="stepA" parent="s1" next="stepB" />
|
||||
<step id="stepB" parent="s2" next="stepC"/>
|
||||
<step id="stepC" parent="s3" />
|
||||
</job></programlisting>In the scenario above, 'step A' will execute
|
||||
first because it is the first <classname>Step</classname> listed. If
|
||||
'step A' completes normally, then 'step B' will execute, and so on.
|
||||
However, if 'step A' fails, then the entire <classname>Job</classname>
|
||||
will fail and 'step B' will not execute.</para>
|
||||
|
||||
<note>
|
||||
<para>With the Spring Batch namespace, the first step listed in the
|
||||
@@ -1214,14 +1192,14 @@ itemWriter.write(items);]]></programlisting>
|
||||
<para>The next element specifies a pattern to match and the step to
|
||||
execute next:</para>
|
||||
|
||||
<para><programlisting><![CDATA[<job id="job">
|
||||
<step id="stepA" parent="s1">
|
||||
<next on="*" to="stepB" />
|
||||
<next on="FAILED" to="stepC" />
|
||||
</step>
|
||||
<step id="stepB" parent="s2" next="stepC" />
|
||||
<step id="stepC" parent="s3" />
|
||||
</job>]]></programlisting></para>
|
||||
<para><programlisting><job id="job">
|
||||
<step id="stepA" parent="s1">
|
||||
<next on="*" to="stepB" />
|
||||
<next on="FAILED" to="stepC" />
|
||||
</step>
|
||||
<step id="stepB" parent="s2" next="stepC" />
|
||||
<step id="stepC" parent="s3" />
|
||||
</job></programlisting></para>
|
||||
|
||||
<para>The "on" attribute of a transition element uses a simple
|
||||
pattern-matching scheme to match the <classname>ExitStatus</classname>
|
||||
@@ -1269,7 +1247,7 @@ itemWriter.write(items);]]></programlisting>
|
||||
it fails, and so on. The example above contains the following 'next'
|
||||
element:</para>
|
||||
|
||||
<programlisting><![CDATA[<next on="FAILED" to="stepB" />]]></programlisting>
|
||||
<programlisting><next on="FAILED" to="stepB" /></programlisting>
|
||||
|
||||
<para>At first glance, it would appear that the 'on' attribute
|
||||
references the <classname>BatchStatus</classname> of the
|
||||
@@ -1286,11 +1264,11 @@ itemWriter.write(items);]]></programlisting>
|
||||
code needs to be different? A good example comes from the skip sample
|
||||
job within the samples project:</para>
|
||||
|
||||
<programlisting><![CDATA[<step id="step1" parent="s1">
|
||||
<end on="FAILED" />
|
||||
<next on="COMPLETED WITH SKIPS" to="errorPrint1" />
|
||||
<next on="*" to="step2" />
|
||||
</step>]]></programlisting>
|
||||
<programlisting><step id="step1" parent="s1">
|
||||
<end on="FAILED" />
|
||||
<next on="COMPLETED WITH SKIPS" to="errorPrint1" />
|
||||
<next on="*" to="step2" />
|
||||
</step></programlisting>
|
||||
|
||||
<para>The above step has three possibilities:</para>
|
||||
|
||||
@@ -1316,12 +1294,12 @@ itemWriter.write(items);]]></programlisting>
|
||||
change the exit code based on the condition of the execution having
|
||||
skipped records:</para>
|
||||
|
||||
<programlisting><![CDATA[public class SkipCheckingListener extends StepExecutionListenerSupport {
|
||||
<programlisting>public class SkipCheckingListener extends StepExecutionListenerSupport {
|
||||
|
||||
public ExitStatus afterStep(StepExecution stepExecution) {
|
||||
String exitCode = stepExecution.getExitStatus().getExitCode();
|
||||
if (!exitCode.equals(ExitStatus.FAILED.getExitCode()) &&
|
||||
stepExecution.getSkipCount() > 0) {
|
||||
if (!exitCode.equals(ExitStatus.FAILED.getExitCode()) &&
|
||||
stepExecution.getSkipCount() > 0) {
|
||||
return new ExitStatus("COMPLETED WITH SKIPS");
|
||||
}
|
||||
else {
|
||||
@@ -1329,7 +1307,7 @@ itemWriter.write(items);]]></programlisting>
|
||||
}
|
||||
}
|
||||
|
||||
}]]></programlisting>
|
||||
}</programlisting>
|
||||
|
||||
<para>The above code is a <classname>StepExecutionListener</classname>
|
||||
that first checks to make sure the <classname>Step</classname> was
|
||||
@@ -1357,7 +1335,7 @@ itemWriter.write(items);]]></programlisting>
|
||||
after the following step executes, the <classname>Job</classname> will
|
||||
end:</para>
|
||||
|
||||
<para><programlisting><![CDATA[<step id="stepC" parent="s3"/>]]></programlisting></para>
|
||||
<para><programlisting><step id="stepC" parent="s3"/></programlisting></para>
|
||||
|
||||
<para>If no transitions are defined for a <classname>Step</classname>,
|
||||
then the <classname>Job</classname>'s statuses will be defined as
|
||||
@@ -1416,14 +1394,14 @@ itemWriter.write(items);]]></programlisting>
|
||||
fails, the <classname>Job</classname> will not be restartable (because
|
||||
the status is COMPLETED).</para>
|
||||
|
||||
<programlisting><![CDATA[<step id="step1" parent="s1" next="step2">
|
||||
<programlisting><step id="step1" parent="s1" next="step2">
|
||||
|
||||
<step id="step2" parent="s2">
|
||||
<end on="FAILED"/>
|
||||
<next on="*" to="step3"/>
|
||||
</step>
|
||||
<step id="step2" parent="s2">
|
||||
<end on="FAILED"/>
|
||||
<next on="*" to="step3"/>
|
||||
</step>
|
||||
|
||||
<step id="step3" parent="s3">]]></programlisting>
|
||||
<step id="step3" parent="s3"></programlisting>
|
||||
</section>
|
||||
|
||||
<section id="failElement">
|
||||
@@ -1447,14 +1425,14 @@ itemWriter.write(items);]]></programlisting>
|
||||
Additionally, if step2 fails, and the <classname>Job</classname> is
|
||||
restarted, then execution will begin again on step2.</para>
|
||||
|
||||
<programlisting><![CDATA[<step id="step1" parent="s1" next="step2">
|
||||
<programlisting><step id="step1" parent="s1" next="step2">
|
||||
|
||||
<step id="step2" parent="s2">
|
||||
<fail on="FAILED" exit-code="EARLY TERMINATION"/>
|
||||
<next on="*" to="step3"/>
|
||||
</step>
|
||||
<step id="step2" parent="s2">
|
||||
<fail on="FAILED" exit-code="EARLY TERMINATION"/>
|
||||
<next on="*" to="step3"/>
|
||||
</step>
|
||||
|
||||
<step id="step3" parent="s3">]]></programlisting>
|
||||
<step id="step3" parent="s3"></programlisting>
|
||||
</section>
|
||||
|
||||
<section id="stopElement">
|
||||
@@ -1472,11 +1450,11 @@ itemWriter.write(items);]]></programlisting>
|
||||
the job will then stop. Once it is restarted, execution will begin on
|
||||
step2.</para>
|
||||
|
||||
<para><programlisting><![CDATA[<step id="step1" parent="s1">
|
||||
<stop on="COMPLETED" restart="step2"/>
|
||||
</step>
|
||||
<para><programlisting><step id="step1" parent="s1">
|
||||
<stop on="COMPLETED" restart="step2"/>
|
||||
</step>
|
||||
|
||||
<step id="step2" parent="s2"/>]]></programlisting></para>
|
||||
<step id="step2" parent="s2"/></programlisting></para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -1489,7 +1467,7 @@ itemWriter.write(items);]]></programlisting>
|
||||
<classname>JobExecutionDecider</classname> can be used to assist in the
|
||||
decision.</para>
|
||||
|
||||
<para><programlisting><![CDATA[public class MyDecider implements JobExecutionDecider {
|
||||
<para><programlisting>public class MyDecider implements JobExecutionDecider {
|
||||
public String decide(JobExecution jobExecution, StepExecution stepExecution) {
|
||||
if (someCondition) {
|
||||
return "FAILED";
|
||||
@@ -1498,24 +1476,24 @@ itemWriter.write(items);]]></programlisting>
|
||||
return "COMPLETED";
|
||||
}
|
||||
}
|
||||
}]]></programlisting></para>
|
||||
}</programlisting></para>
|
||||
|
||||
<para>In the job configuration, a "decision" tag will specify the
|
||||
decider to use as well as all of the transitions.</para>
|
||||
|
||||
<para><programlisting><![CDATA[<job id="job">
|
||||
<step id="step1" parent="s1" next="decision" />
|
||||
<para><programlisting><job id="job">
|
||||
<step id="step1" parent="s1" next="decision" />
|
||||
|
||||
<decision id="decision" decider="decider">
|
||||
<next on="FAILED" to="step2" />
|
||||
<next on="COMPLETED" to="step3" />
|
||||
</decision>
|
||||
<decision id="decision" decider="decider">
|
||||
<next on="FAILED" to="step2" />
|
||||
<next on="COMPLETED" to="step3" />
|
||||
</decision>
|
||||
|
||||
<step id="step2" parent="s2" next="step3"/>
|
||||
<step id="step3" parent="s3" />
|
||||
</job>
|
||||
<step id="step2" parent="s2" next="step3"/>
|
||||
<step id="step3" parent="s3" />
|
||||
</job>
|
||||
|
||||
<beans:bean id="decider" class="com.MyDecider"/>]]></programlisting></para>
|
||||
<beans:bean id="decider" class="com.MyDecider"/></programlisting></para>
|
||||
</section>
|
||||
|
||||
<section id="split-flows">
|
||||
@@ -1532,16 +1510,95 @@ itemWriter.write(items);]]></programlisting>
|
||||
elements such as the 'next' attribute or the 'next', 'end', 'fail', or
|
||||
'pause' elements.</para>
|
||||
|
||||
<programlisting><![CDATA[<split id="split1" 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"/>]]></programlisting>
|
||||
<programlisting><split id="split1" 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"/></programlisting>
|
||||
</section>
|
||||
|
||||
<section id="split-flows">
|
||||
<title>Externalizing Flow Definitions and Dependencies Between
|
||||
Jobs</title>
|
||||
|
||||
<para>Part of the flow in a job can be externalized as a separate bean
|
||||
definition, and then re-used. There are three ways to do this, and the
|
||||
first is to simply declare the flow as a reference to one defined
|
||||
elsewhere:</para>
|
||||
|
||||
<programlisting><job id="job">
|
||||
<flow id="job1.flow1" parent="flow1" next="step3"/>
|
||||
<step id="step3" parent="s3"/>
|
||||
</job>
|
||||
|
||||
<flow id="flow1">
|
||||
<step id="step1" parent="s1" next="step2"/>
|
||||
<step id="step2" parent="s2"/>
|
||||
</flow></programlisting>
|
||||
|
||||
<para>The effect of defining an external flow like this is simply to
|
||||
insert the steps from the external flow into the job as if they had been
|
||||
declared inline. In this way many jobs can refer to the same template
|
||||
flow and compose such templates into different logical flows. This is
|
||||
also a good way to separate the integration testing of the individual
|
||||
flows.</para>
|
||||
|
||||
<para>The second form of an externalized flow is to use a
|
||||
<classname>FlowStep</classname>. A <classname>FlowStep</classname> is an
|
||||
implementation of the <classname>Step</classname> interface that
|
||||
delegates processing to a flow defined as above with a
|
||||
<literal><flow/></literal> element in XML. There is also support
|
||||
for creating a FlowStep in XML directly:</para>
|
||||
|
||||
<para><programlisting><job id="job">
|
||||
<step id="job1.flow1" flow="flow1" next="step3"/>
|
||||
<step id="step3" parent="s3"/>
|
||||
</job>
|
||||
|
||||
<flow id="flow1">
|
||||
<step id="step1" parent="s1" next="step2"/>
|
||||
<step id="step2" parent="s2"/>
|
||||
</flow></programlisting>The logic of execution of this job is the same
|
||||
as the previous example, but the data stored in the job repository is
|
||||
different: the <classname>Step</classname> "job1.flow1" gets its own
|
||||
entry in the repository. This can be useful for monitoring and reporting
|
||||
purposes, and moreover it can be used to give more structure to a <link
|
||||
linkend="partitioning">partitioned step</link>.</para>
|
||||
|
||||
<para>The third form of an externalized flow is to use a
|
||||
<classname>JobStep</classname>. A <classname>JobStep</classname> is
|
||||
similar to a <classname>FlowStep</classname>, but actually creates and
|
||||
launches a separate job execution for the steps in the flow specified.
|
||||
Here is an example:</para>
|
||||
|
||||
<para><programlisting><job id="jobStepJob" restartable="true">
|
||||
<step id="jobStepJob.step1" parent="jobStep"/>
|
||||
</job>
|
||||
|
||||
<job id="<emphasis role="bold">job</emphasis>" restartable="true">...</job>
|
||||
|
||||
<bean id="jobStep" class="org.springframework.batch.core.step.job.JobStep">
|
||||
<property name="jobRepository" ref="jobRepository"/>
|
||||
<property name="jobLauncher" ref="jobLauncher"/>
|
||||
<property name="job" ref="<emphasis role="bold">job</emphasis>"/>
|
||||
<property name="jobParametersExtractor">
|
||||
<bean class="org.springframework.batch.core.step.job.DefaultJobParametersExtractor">
|
||||
<property name="keys" value="input.file"/>
|
||||
</bean>
|
||||
</property>
|
||||
</bean></programlisting></para>
|
||||
|
||||
<para>Again this is useful when you want to have more granular options
|
||||
for monitoring and reporting on jobs and steps. Using
|
||||
<classname>JobStep</classname> is also often a good answer to the
|
||||
question: "How do I create dependencies between jobs?". It is a good way
|
||||
to break up a large system into smaller modules and control the flow of
|
||||
jobs.</para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -1555,11 +1612,11 @@ itemWriter.write(items);]]></programlisting>
|
||||
Flat File resources can be configured using standard Spring
|
||||
constructs:</para>
|
||||
|
||||
<programlisting><![CDATA[<bean id="flatFileItemReader"
|
||||
class="org.springframework.batch.item.file.FlatFileItemReader">
|
||||
<property name="resource"
|
||||
value="file://outputs/20070122.testStream.CustomerReportStep.TEMP.txt" />
|
||||
</bean>]]></programlisting>
|
||||
<programlisting><bean id="flatFileItemReader"
|
||||
class="org.springframework.batch.item.file.FlatFileItemReader">
|
||||
<property name="resource"
|
||||
value="file://outputs/20070122.testStream.CustomerReportStep.TEMP.txt" />
|
||||
</bean></programlisting>
|
||||
|
||||
<para>The above <classname>Resource</classname> will load the file from
|
||||
the file system location specified. Note that absolute locations have to
|
||||
@@ -1569,10 +1626,10 @@ itemWriter.write(items);]]></programlisting>
|
||||
at runtime as a parameter to the job. This could be solved using '-D'
|
||||
parameters, i.e. a system property:</para>
|
||||
|
||||
<programlisting><![CDATA[<bean id="flatFileItemReader"
|
||||
class="org.springframework.batch.item.file.FlatFileItemReader">
|
||||
<property name="resource" value="${input.file.name}" />
|
||||
</bean>]]></programlisting>
|
||||
<programlisting><bean id="flatFileItemReader"
|
||||
class="org.springframework.batch.item.file.FlatFileItemReader">
|
||||
<property name="resource" value="${input.file.name}" />
|
||||
</bean></programlisting>
|
||||
|
||||
<para>All that would be required for this solution to work would be a
|
||||
system argument (-Dinput.file.name="file://file.txt"). (Note that although
|
||||
@@ -1632,17 +1689,17 @@ itemWriter.write(items);]]></programlisting>
|
||||
must be added explicitly, either by using the <literal>batch</literal>
|
||||
namespace:</para>
|
||||
|
||||
<programlisting><![CDATA[<beans:beans xmlns="http://www.springframework.org/schema/beans"
|
||||
<programlisting><beans:beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:batch="http://www.springframework.org/schema/batch"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="...">
|
||||
xsi:schemaLocation="...">
|
||||
...
|
||||
</beans:beans>]]></programlisting>
|
||||
</beans:beans></programlisting>
|
||||
|
||||
<para>or by including a bean definition explicitly for
|
||||
the<classname>Step</classname> (but not both):</para>
|
||||
|
||||
<programlisting><![CDATA[<bean class="org.springframework.batch.core.scope.StepScope" />]]></programlisting>
|
||||
<programlisting><bean class="org.springframework.batch.core.scope.StepScope" /></programlisting>
|
||||
</section>
|
||||
</section>
|
||||
</chapter>
|
||||
</chapter>
|
||||
|
||||
Reference in New Issue
Block a user