BATCH-1056: fixed errors

This commit is contained in:
dhgarrette
2009-02-03 23:14:51 +00:00
parent 7487b4dfd2
commit 8e56304eb4
7 changed files with 242 additions and 275 deletions

View File

@@ -10,15 +10,13 @@
<table>
<title>Available Item Readers</title>
<tgroup cols="3">
<tgroup cols="2">
<colspec align="center" />
<thead>
<row>
<entry align="center">Item Reader</entry>
<entry align="center">Type of Item Provided</entry>
<entry align="center">Description</entry>
</row>
</thead>
@@ -28,8 +26,6 @@
<entry
align="left">AbstractItemCountingItemStreamItemReader</entry>
<entry align="left">java.lang.Object</entry>
<entry align="left">Abstract base class that provides basic
restart capabilities by counting the number of items returned from
an <classname>ItemReader</classname>.</entry>
@@ -38,8 +34,6 @@
<row>
<entry align="left">ListItemReader</entry>
<entry align="left">java.lang.Object</entry>
<entry align="left">Provides the items from a list, one at a
time</entry>
</row>
@@ -47,8 +41,6 @@
<row>
<entry align="left">ItemReaderAdapter</entry>
<entry align="left">java.lang.Object</entry>
<entry align="left">Adapts any class to the
<classname>ItemReader</classname> interface.</entry>
</row>
@@ -56,8 +48,6 @@
<row>
<entry align="left">AggregateItemReader</entry>
<entry align="left">java.util.Collection</entry>
<entry align="left">An ItemReader that delivers a list as its
item, storing up objects from the injected ItemReader until they
are ready to be packed out as a collection. This ItemReader should
@@ -71,9 +61,7 @@
<row>
<entry align="left">FlatFileItemReader</entry>
<entry align="left">java.lang.String</entry>
<entry align="left">Reads from a flat file, includes ItemStream
<entry align="left">Reads from a flat file. Includes ItemStream
and Skippable functionality. See section on Read from a
File</entry>
</row>
@@ -81,8 +69,6 @@
<row>
<entry align="left">StaxEventItemReader</entry>
<entry align="left">java.lang.Object</entry>
<entry align="left">Reads via StAX. See HOWTO - Read from a
File</entry>
</row>
@@ -90,45 +76,28 @@
<row>
<entry align="left">JdbcCursorItemReader</entry>
<entry align="left">java.lang.Object</entry>
<entry align="left">Reads from a database cursor via JDBC. See
HOWTO - Read from a Database</entry>
</row>
<row>
<entry align="left">DrivingQueryItemReader</entry>
<entry align="left">java.lang.Object</entry>
<entry align="left">Base class for operations that read from a
database based on a single driving query. Configured by injecting
a KeyGenerator object. See HOWTO - Read from a Database</entry>
</row>
<row>
<entry align="left">HibernateCursorItemReader</entry>
<entry align="left">java.lang.Object</entry>
<entry align="left">Reads from a cursor based on an HQL query. See
section on Reading from a Database</entry>
</row>
<row>
<entry align="left">IbatisDrivingQueryItemReader</entry>
<entry align="left">IbatisPagingItemReader</entry>
<entry align="left">java.lang.Object</entry>
<entry align="left">Reads via iBATIS based on a driving query. See
HOWTO - Read from a Database</entry>
<entry align="left">Reads via iBATIS based on a query. Pages
through the rows so that large datasets can be read without
running out of memory. See HOWTO - Read from a Database</entry>
</row>
<row>
<entry align="left">JmsItemReader</entry>
<entry align="left">javax.jms.Message</entry>
<entry align="left">Given a Spring JmsOperations object and a JMS
Destination or destination name to send errors, provides items
received through the injected JmsOperations receive()
@@ -138,8 +107,6 @@
<row>
<entry align="left">JpaPagingItemReader</entry>
<entry align="left">java.lang.Object</entry>
<entry align="left">Given a JPQL statement, pages through the
rows, such that large datasets can be read without running out of
memory</entry>
@@ -148,8 +115,6 @@
<row>
<entry align="left">JdbcPagingItemReader</entry>
<entry align="left">java.lang.Object</entry>
<entry align="left">Given a SQL statement, pages through the rows,
such that large datasets can be read without running out of
memory</entry>
@@ -165,15 +130,13 @@
<table>
<title>Available Item Writers</title>
<tgroup cols="3">
<tgroup cols="2">
<colspec align="center" />
<thead>
<row>
<entry align="center">Item Writer</entry>
<entry align="center">Type of Item Written</entry>
<entry align="center">Description</entry>
</row>
</thead>
@@ -182,29 +145,14 @@
<row>
<entry align="left">AbstractItemStreamItemWriter</entry>
<entry align="left">java.lang.Object</entry>
<entry align="left">Abstract base class that combines the
<classname>ItemStream</classname> and
<classname>ItemWriter</classname> interfaces.</entry>
</row>
<row>
<entry align="left">BatchSqlUpdateItemWriter</entry>
<entry align="left">java.lang.Object</entry>
<entry align="left"><classname>ItemWriter</classname> that uses
batching freatures from a <classname>PreparedStatement</classname>
if available and can take rudimentary steps to locate a failure
during a <methodname>flush</methodname>.</entry>
</row>
<row>
<entry align="left">CompositeItemWriter</entry>
<entry align="left">java.lang.Object</entry>
<entry align="left">Passes an item to the process method of each
in an injected <emphasis role="bold">List</emphasis> of <emphasis
role="bold">ItemWriter</emphasis> objects</entry>
@@ -213,8 +161,6 @@
<row>
<entry align="left">ItemWriterAdapter</entry>
<entry align="left">java.lang.Object</entry>
<entry align="left">Adapts any class to the
<classname>ItemWriter</classname> interface.</entry>
</row>
@@ -222,8 +168,6 @@
<row>
<entry align="left">PropertyExtractingDelegatingItemWriter</entry>
<entry align="left">java.lang.Object</entry>
<entry align="left">Extends AbstractMethodInvokingDelegator
creating arguments on the fly. Arguments are created by retrieving
the values from the fields in the item to be processed (via a
@@ -234,19 +178,12 @@
<row>
<entry align="left">FlatFileItemWriter</entry>
<entry align="left">java.lang.Object</entry>
<entry align="left">Attempts to convert the item to a <emphasis
role="bold">String</emphasis>, <emphasis
role="bold">Collection</emphasis> or array using an injected
<emphasis role="bold">Coverter</emphasis> and then
recurses.</entry>
<entry align="left">Writes to a flat file. Includes ItemStream and
Skippable functionality. See section on Writing to a File</entry>
</row>
<row>
<entry align="left">HibernateAwareItemWriter</entry>
<entry align="left">java.lang.Object</entry>
<entry align="left">HibernateItemWriter</entry>
<entry align="left">This item writer is hibernate session aware
and handles some transaction-related work that a non-"hibernate
@@ -255,9 +192,16 @@
</row>
<row>
<entry align="left">JpaAwareItemWriter</entry>
<entry align="left">JdbcBatchItemWriter</entry>
<entry align="left">java.lang.Object</entry>
<entry align="left">Uses batching freatures from a
<classname>PreparedStatement</classname>, if available, and can
take rudimentary steps to locate a failure during a
<methodname>flush</methodname>.</entry>
</row>
<row>
<entry align="left">JpaItemWriter</entry>
<entry align="left">This item writer is JPA EntityManager aware
and handles some transaction-related work that a non-"jpa aware"
@@ -266,9 +210,7 @@
</row>
<row>
<entry align="left">StaxEventWriterItemWriter</entry>
<entry align="left">java.lang.Object</entry>
<entry align="left">StaxEventItemWriter</entry>
<entry align="left">Uses an <emphasis
role="bold">ObjectToXmlSerializer</emphasis> implementation to

View File

@@ -5,29 +5,31 @@
<title>Common Batch Patterns</title>
<para>Some batch jobs can be assembled purely from off-the-shelf components
in Spring Batch, mostly the <classname>ItemReader</classname> and
<classname>ItemWriter</classname> implementations. Where this is not
possible (the majority of cases) the main API entry points for application
in Spring Batch. For instance the <classname>ItemReader</classname> and
<classname>ItemWriter</classname> implementations can be configured to cover
a wide range of scenarios. However, for the majority of cases, custom code
will have to be written. The main API entry points for application
developers are the <classname>Tasklet</classname>,
<classname>ItemReader</classname>, <classname>ItemWriter</classname> and the
various listener interfaces. Most simple batch jobs will be able to use
off-the-shelf input from a Spring Batch <classname>ItemReader</classname>,
but it is very often the case that there are custom concerns in the
processing and writing, which normally leads developers to implement an
<classname>ItemWriter</classname>, or
<classname>ItemTransformer</classname>.</para>
but it is often the case that there are custom concerns in the processing
and writing, which require developers to implement an
<classname>ItemWriter</classname> or
<classname>ItemProcessor</classname>.</para>
<para>Here we provide a few examples of common patterns in custom business
logic, mainly using the listener interfaces . It should be noted that an
<classname>ItemReader</classname> or <classname>ItemWriter</classname> can
implement the listener interfaces as well if appropriate.</para>
<para>Here, we provide a few examples of common patterns in custom business
logic. These examples primarily feature the listener interfaces. It should
be noted that an <classname>ItemReader</classname> or
<classname>ItemWriter</classname> can implement a listener interface as
well, if appropriate.</para>
<section>
<title>Logging Item Processing and Failures</title>
<para>A common use case is the need for special handling of errors in a
step, item by item, perhaps logging to a special channel, or inserting a
record into a database. The <classname>StepHandlerStep</classname>
record into a database. A chunk-oriented <classname>Step</classname>
(created from the step factory beans) allows users to implement this use
case with a simple <classname>ItemReadListener</classname>, for errors on
read, and an <classname>ItemWriteListener</classname>, for errors on
@@ -71,14 +73,16 @@
<section>
<title>Stopping a Job Manually for Business Reasons</title>
<para>Spring Batch provides a stop() method through the JobLauncher
interface, but this is really aimed at the operator, rather than the
application programmer. Sometimes it is more convenient or makes more
sense to stop a job execution from within the business logic.</para>
<para>Spring Batch provides a <methodname>stop</methodname>() method
through the <classname>JobLauncher</classname> interface, but this is
really for use by the operator rather than the application programmer.
Sometimes it is more convenient or makes more sense to stop a job
execution from within the business logic.</para>
<para>The simplest thing to do is to throw a RuntimeException (one that
isn't retried indefinitely or skipped), For example, a custom exception
type could be used, as in the example below:</para>
<para>The simplest thing to do is to throw a
<classname>RuntimeException</classname> (one that isn't retried
indefinitely or skipped). For example, a custom exception type could be
used, as in the example below:</para>
<programlisting>public class PoisonPillItemWriter implements ItemWriter&lt;T&gt; {
@@ -95,15 +99,15 @@
<para>Another simple way to stop a step from executing is to simply return
<code>null</code> from the <classname>ItemReader</classname>:</para>
<programlisting>public class EarlyCompletionItemReader extends AbstractItemReader {
<programlisting>public class EarlyCompletionItemReader implements ItemReader&lt;T&gt; {
private ItemReader delegate;
private ItemReader&lt;T&gt; delegate;
public void setDelegate(ItemReader delegate) { ... }
public void setDelegate(ItemReader&lt;T&gt; delegate) { ... }
public Object read() throws Exception {
public T read() throws Exception {
Object item = delegate.read();
T item = delegate.read();
if (isEndItem(item)) {
return null; // end the step here
@@ -138,9 +142,10 @@
<classname>StepExecution</classname>, which is checked by the
<classname>Step</classname> implementations in the framework in between
item processing. To implement this alternative, we need access to the
current StepExecution, and this can be achieved by implementing a
StepListener and registering it with the Step. Here is an example of a
listener that sets the flag:</para>
current <classname>StepExecution</classname>, and this can be achieved by
implementing a <classname>StepListener</classname> and registering it with
the <classname>Step</classname>. Here is an example of a listener that
sets the flag:</para>
<programlisting>public class CustomItemWriter extends ItemListenerSupport implements StepListener {
@@ -160,7 +165,7 @@
}</programlisting>
<para>The default behaviour here when the flag is set is for the step to
<para>The default behavior here when the flag is set is for the step to
throw a <classname>JobInterruptedException</classname>. This can be
controlled through the <classname>StepInterruptionPolicy</classname>, but
the only choice is to throw or not throw an exception, so this is always
@@ -172,7 +177,7 @@
<para>A very common requirement is to aggregate information during the
output process and to append a record at the end of a file summarizing the
data, or providing a checksum. This can also be achieved with a callbacks
data, or providing a checksum. This can also be achieved with a callback
in the step, normally as part of a custom
<classname>ItemWriter</classname>. In this case, since a job is
accumulating state that should not be lost if the job aborts, the
@@ -206,10 +211,8 @@
}
public void write(Trade item) {
delegate.write(item);
totalAmount += item.getAmount();
}
}</programlisting>
@@ -220,8 +223,9 @@
<classname>ExecutionContext</classname>. In this way we can be sure that
when the <code>open()</code> callback is received on a restart. The
framework garuntees we always get the last value that was committed. It
should be noted that it is not always necessary to implement ItemStream.
For example, if the ItemWriter is re-runnable, in the sense that it
should be noted that it is not always necessary to implement
<classname>ItemStream</classname>. For example, if the
<classname>ItemWriter</classname> is re-runnable, in the sense that it
maintains its own state in a transactional resource like a database, there
is no need to maintain state within the writer itself.</para>
</section>
@@ -272,11 +276,9 @@
</imageobject>
</mediaobject>
<para>As you can see, an existing DAO can be used to obtain a full 'Foo'
object using the key obtained from the driving query. In Spring Batch,
driving query style input is implemented with a
<classname>DrivingQueryItemReader</classname>, which has only one
dependency: a <classname>KeyCollector</classname></para>
<para>An ItemProcessor should be used to transform the key obtained from
the driving query into a full 'Foo' object. An existing DAO can be used to
query for the full object based on the key.</para>
</section>
<section id="multiLineRecords">

View File

@@ -389,7 +389,7 @@
will be kicked off again for 01-01, starting where it left off and
completing successfully at 9:30. Because it's now the next day, the
01-02 job must be run as well, which is kicked off just afterwards at
9:31, and completes in it's normal one hour time at 10:30. There is no
9:31, and completes in its normal one hour time at 10:30. There is no
requirement that one <classname>JobInstance</classname> be kicked off
after another, unless there is potential for the two jobs to attempt to
access the same data, causing issues with locking at the database level.
@@ -573,7 +573,7 @@
<classname>JobExecution</classname>. However, if a step fails to execute
because the step before it fails, there will be no execution persisted
for it. A <classname>StepExecution</classname> will only be created when
it's <classname>Step</classname> is actually started.</para>
its <classname>Step</classname> is actually started.</para>
<para>Step executions are represented by objects of the
<classname>StepExecution</classname> class. Each execution contains a

View File

@@ -99,8 +99,8 @@
<glossdef>
<para>An item represents the smallest ammount of complete data for
processing. In the most simple terms this might mean a line in a file,
a row in a database table, or a particular element in an XML
processing. In the simplest terms, this might mean a line in a file, a
row in a database table, or a particular element in an XML
file.</para>
</glossdef>
</glossentry>
@@ -119,7 +119,7 @@
<glossterm>Commit Interval</glossterm>
<glossdef>
<para>A set of LUWs constitute a commit interval.</para>
<para>A set of LUWs processed within a single transaction.</para>
</glossdef>
</glossentry>
@@ -147,7 +147,7 @@
<glossterm>Restartable</glossterm>
<glossdef>
<para>- a job that can be executed again and will assume the same
<para>A job that can be executed again and will assume the same
identity as when run initially. In othewords, it is has the same job
instance id.</para>
</glossdef>
@@ -157,14 +157,14 @@
<glossterm>Rerunnable</glossterm>
<glossdef>
<para>a job that is restartable and manages it's own state in terms of
<para>A job that is restartable and manages its own state in terms of
previous run's record processing. An example of a rerunnable step is
one based on a driving query. If the driving query can be formed so
that it will limit the processed rows when the job is restarted than
it is re-runnable. This is managed by the application logic. Often
times a condition is added to the where statement to limit the rows
returned by the driving query with something like "and processedFlag
!= true". </para>
!= true".</para>
</glossdef>
</glossentry>
@@ -189,9 +189,8 @@
calling a block of code, retry is stateful, and continually calls the
same block of code with the same input, until it either succeeds, or
some type of retry limit has been exceeded. It is only generally
useful if the operation is non-deterministic meaning that a retry on a
subsequent invocation might succeed because something in the
environment has improved.</para>
useful if a subsequent invocation of the operation might succeed
because something in the environment has improved.</para>
</glossdef>
</glossentry>
@@ -214,4 +213,4 @@
</glossdef>
</glossentry>
</glossdiv>
</glossary>
</glossary>

View File

@@ -20,20 +20,21 @@
</imageobject>
</mediaobject>
<para>While the Job object may seem like a simple container for steps, there
are many configuration options that developers should be aware of.
Furthermore, there are many considerations for how a
<classname>Job</classname> will be run and how its meta data will be stored
during that run. This chapter will explain the various configuration options
and runtime concerns of a <classname>Job</classname>.</para>
<para>While the <classname>Job</classname> object may seem like a simple
container for steps, there are many configuration options of which a
developers must be aware . Furthermore, there are many considerations for
how a <classname>Job</classname> will be run and how its meta-data will be
stored during that run. This chapter will explain the various configuration
options and runtime concerns of a <classname>Job</classname>.</para>
<section>
<title>Configuring a Job</title>
<para>There are multiple implementations of the <link
linkend="job">Job</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 Steps.</para>
linkend="job"><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>
<programlisting>
&lt;job id="footballJob"&gt;
@@ -57,20 +58,26 @@
</programlisting>
<para>Note that if the job repository's default id is overridden, it must
be explicitly specified on <emphasis role="bold">every</emphasis>
<classname>Step</classname> <emphasis role="bold">as well as</emphasis> on
the <classname>Job</classname>.</para>
<section>
<title>Restartability</title>
<para>One key concern when execution a batch job, is what happens when a
failed job is restarted? A Job is considered to have been 'restarted' if
the same <classname>JobInstance</classname> has more than one
JobExecution. Ideally, all jobs should be able to start up where they
left off, but there are scenarios where this is not possible. <emphasis
role="bold">It is entirely up to the developer to ensure that a new
instance is always created in this scenario</emphasis>. However, Spring
Batch does provide some help. If a Job should never be restarted, but
should always be run as part of a new
<classname>JobInstance</classname>, then the restartable property may be
set to 'false':</para>
<para>One key issue when execution 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
<classname>JobInstance</classname>. Ideally, all jobs should be able to
start up where they left off, but there are scenarios where this is not
possible. <emphasis role="bold">It is entirely up to the developer to
ensure that a new JobInstance is created in this scenario</emphasis>.
However, Spring Batch does provide some help. If a
<classname>Job</classname> should never be restarted, but should always
be run as part of a new <classname>JobInstance</classname>, then the
restartable property may be set to 'false':</para>
<programlisting>
&lt;job id="footballJob" <emphasis role="bold">restartable="false"</emphasis>&gt;
@@ -131,8 +138,9 @@
</programlisting>
<para>Listeners can be added to a <classname>SimpleJob</classname> via
the setJobListeners property:</para>
<para><classname>JobListener</classname>s can be added to a
<classname>SimpleJob</classname> via the listeners element on the
job:</para>
<programlisting>
&lt;job id="footballJob"&gt;
@@ -146,17 +154,17 @@
</programlisting>
<para>It should be noted that afterJob will be called regardless of the
success or failure of the <classname>Job</classname>. If success or
failure needs to be determined it can be obtained from the
<classname>JobExecution</classname>:</para>
<para>It should be noted that <methodname>afterJob</methodname> will be
called regardless of the success or failure of the
<classname>Job</classname>. If success or failure needs to be determined
it can be obtained from the <classname>JobExecution</classname>:</para>
<programlisting>
void afterJob(JobExecution jobExecution){
if( jobExecution.getStatus = BatchStatus.COMPLETED ){
if( jobExecution.getStatus() == BatchStatus.COMPLETED ){
//job success
}
else if(jobExecution.getStatus = BatchStatus.FAILED){
else if(jobExecution.getStatus() == BatchStatus.FAILED){
//job failure
}
}
@@ -168,14 +176,14 @@
<title>JobFactory and Stateful Components in Steps</title>
<para>Unlike many traditional Spring applications, many of the
components of a batch application are stateful, the file readers and
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
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>
@@ -196,12 +204,13 @@
<title>Configuring a JobRepository</title>
<para>As described in earlier, the <link
linkend="jobRepository">JobRepository</link> is used for basic CRUD
operations of the various persisted domain objects within Spring Batch,
such as JobExecution and StepExecution. It is required by many of the
major framework features, such as the <classname>JobLauncher</classname>,
linkend="jobRepository"><classname>JobRepository</classname></link> is
used for basic CRUD operations of the various persisted domain objects
within Spring Batch, such as <classname>JobExecution</classname> and
<classname>StepExecution</classname>. It is required by many of the major
framework features, such as the <classname>JobLauncher</classname>,
<classname>Job</classname>, and <classname>Step</classname>. The batch
namespace abstract much of the implementation details of the
namespace abstracts away many of the implementation details of the
<classname>JobRepository</classname> implementations and their
collaborators. However, there are still a few configuration options
available:</para>
@@ -226,7 +235,7 @@
<para>If the namespace is used, transactional advice will be
automatically created around the repository. This is to ensure that the
batch meta data, including state that is necessary for restarts after a
failure, is persisted correctly. The behaviour of the framework is not
failure, is persisted correctly. The behavior of the framework is not
well defined if the repository methods are not transactional. The
isolation level in the <code>create*</code> method attributes is
specified separately to ensure that when jobs are launched, if two
@@ -246,7 +255,7 @@
</programlisting></para>
<para>If the namespace or factory beans aren't used then it is also
essential to configure the transactional behaviour of the repository
essential to configure the transactional behavior of the repository
using AOP:</para>
<para><programlisting>
@@ -266,7 +275,7 @@
<para>This fragment can be used as is, with almost no changes. Remember
also to include the appropriate namespace declarations and to make sure
spring-tx and spring-aop (or the whole of spring) is on the
spring-tx and spring-aop (or the whole of spring) are on the
classpath.</para>
</section>
@@ -294,7 +303,7 @@
as SYSTEM.TEST_JOB_EXECUTION.</para>
<note>
<para>Only the table prefix is configurable, the table and column
<para>Only the table prefix is configurable. The table and column
names are not.</para>
</note>
</section>
@@ -303,10 +312,11 @@
<title>In-Memory Repository</title>
<para>There are scenarios in which you may not want to persist your
domain objects to the database. One reason may be speed, storing domain
domain objects to the database. One reason may be speed; storing domain
objects at each commit point takes extra time. Another reason may be
that you just don't need to persist status for a particular job. Spring
batch provides a solution:</para>
that you just don't need to persist status for a particular job. For
this reason, Spring batch provides an in-memory Map version of the job
respository:</para>
<programlisting> &lt;bean id="jobRepository"
class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean" /&gt;</programlisting>
@@ -318,7 +328,7 @@
<para>The most basic implementation of the
<classname>JobLauncher</classname> interface is the
<classname>SimpleJobLauncher</classname>. It's only required dependency is
<classname>SimpleJobLauncher</classname>. Its only required dependency is
a <classname>JobRepository</classname>, in order to obtain an
execution:</para>
@@ -327,10 +337,11 @@
&lt;property name="jobRepository" ref="jobRepository" /&gt;
&lt;/bean&gt;</programlisting>
<para>Once a <link linkend="jobExecution">JobExecution</link> is obtained,
it is passed to the execute method of <classname>Job</classname>,
ultimately returning the <classname>JobExecution</classname> to the
caller:</para>
<para>Once a <link
linkend="jobExecution"><classname>JobExecution</classname></link> is
obtained, it is passed to the execute method of
<classname>Job</classname>, ultimately returning the
<classname>JobExecution</classname> to the caller:</para>
<mediaobject>
<imageobject role="html">
@@ -346,13 +357,13 @@
</imageobject>
</mediaobject>
<para>The sequence is straightforward, and works well when launched from a
scheduler, but causes issues when trying to launch from an HTTP request.
In this scenario, the launching needs to be done asynchronously, so that
the <classname>SimpleJobLauncher</classname> returns immediately to it's
caller. This is because it is not good practice to keep an HTTP request
open for the amount of time needed by long running processes such as
batch. An example sequence is below:</para>
<para>The sequence is straightforward and works well when launched from a
scheduler. However, issues arise when trying to launch from an HTTP
request. In this scenario, the launching needs to be done asynchronously
so that the <classname>SimpleJobLauncher</classname> returns immediately
to its caller. This is because it is not good practice to keep an HTTP
request open for the amount of time needed by long running processes such
as batch. An example sequence is below:</para>
<mediaobject>
<imageobject role="html">
@@ -388,16 +399,16 @@
<section>
<title>Running a Job</title>
<para>At a minimum, launching a batch job requires two things: the Job to
be launched and a <classname>JobLauncher</classname>. Both can be
contained within the same context or different contexts. For example, if
launching a job from the command line, a new JVM will be instantiated for
each Job, and thus every job will have it's own
<classname>JobLauncher</classname>. However, if running from within a web
container within the scope of an <classname>HttpRequest</classname>, there
will usually be one <classname>JobLauncher</classname>, configured for
asynchronous job launching, that multiple requests will invoke to launch
their jobs.</para>
<para>At a minimum, launching a batch job requires two things: the
<classname>Job</classname> to be launched and a
<classname>JobLauncher</classname>. Both can be contained within the same
context or different contexts. For example, if launching a job from the
command line, a new JVM will be instantiated for each Job, and thus every
job will have its own <classname>JobLauncher</classname>. However, if
running from within a web container within the scope of an
<classname>HttpRequest</classname>, there will usually be one
<classname>JobLauncher</classname>, configured for asynchronous job
launching, that multiple requests will invoke to launch their jobs.</para>
<section>
<title>Running Jobs from the Command Line</title>
@@ -421,29 +432,32 @@
<classname>CommandLineJobRunner</classname>. It's important to note
that this is just one way to bootstrap your application, but there are
many ways to launch a Java process, and this class should in no way be
viewed as definitive. It performs four tasks:</para>
viewed as definitive. The <classname>CommandLineJobRunner</classname>
performs four tasks:</para>
<itemizedlist>
<listitem>
<para>Loads the appropriate Application Context</para>
<para>Load the appropriate
<classname>ApplicationContext</classname></para>
</listitem>
<listitem>
<para>Parses command line arguments into JobParameters</para>
<para>Parse command line arguments into
<classname>JobParameters</classname></para>
</listitem>
<listitem>
<para>Locates the appropriate job based on arguments</para>
<para>Locate the appropriate job based on arguments</para>
</listitem>
<listitem>
<para>Uses the JobLauncher provided in the application context to
launch the job.</para>
<para>Use the <classname>JobLauncher</classname> provided in the
application context to launch the job.</para>
</listitem>
</itemizedlist>
<para>All of these tasks are accomplished based completely upon the
arguments passed in. The following are required arguments:</para>
<para>All of these tasks are accomplished using only the arguments
passed in. The following are required arguments:</para>
<table>
<title>CommandLineJobRunner arguments</title>
@@ -479,22 +493,21 @@
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
Job. The second argument, 'endOfDay' represents the job name. The
final argument, 'schedule.date=01-01-2008' will be converted into
<classname>JobParameters</classname>. An example of the XML
configuration is below:</para>
<classname>Job</classname>. The second argument, 'endOfDay' represents
the job name. The final argument, 'schedule.date=01-01-2008' will be
converted into <classname>JobParameters</classname>. An example of the
XML configuration is below:</para>
<programlisting> &lt;bean id="endOfDay"
class="org.springframework.batch.core.job.SimpleJob"&gt;
&lt;property name="steps"&gt;
&lt;bean id="step1" parent="simpleStep" /&gt;
<programlisting> &lt;job id="endOfDay"&gt;
&lt;steps&gt;
&lt;step name="step1" parent="simpleStep" /&gt;
&lt;!-- Step details removed for clarity --&gt;
&lt;/property&gt;
&lt;/bean&gt;
&lt;/steps&gt;
&lt;/job&gt;
&lt;!-- Launcher details removed for clarity --&gt;
&lt;bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher" /&gt;</programlisting>
&lt;beans: bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher" /&gt;</programlisting>
<para>This example is overly simplistic, since there are many more
requirements to a run a batch job in Spring Batch in general, but it
@@ -507,22 +520,23 @@
<section>
<title>ExitCodes</title>
<para>When launching a batch job from the command-line, it is often
from an enterprise scheduler. Most schedulers are fairly dumb, and
work only at the process level. Meaning, they only know about some
<para>When launching a batch job from the command-line, an enterprise
scheduler is often used. Most schedulers are fairly dumb and work only
at the process level. This means that they only know about some
operating system process such as a shell script that they're invoking.
In this scenario, the only way to communicate back to the scheduler
about the success or failure of a job is through return codes. A
number is returned to a scheduler that is told how to interpret the
result. In the simple case: 0 is success and 1 is failure. However,
there may be scenarios such as: If job A returns 4 kick off job B, if
it returns 5 kick off job C. This type of behavior is configured at
the scheduler level, but it is important that a processing framework
such as Spring Batch provide a way to return a numeric representation
of of the 'Exit Code' for a particular batch job. In Spring Batch this
is encapsulated within an <classname>ExitStatus</classname>, which is
covered in more detail in Chapter 5. For the purposes of discussing
exit codes, the only important thing to know is that an
return code is a number that is returned to a scheduler by the process
that indicates the result of the run. In the simplest case: 0 is
success and 1 is failure. However, there may be more complex
scenarios: If job A returns 4 kick off job B, and if it returns 5 kick
off job C. This type of behavior is configured at the scheduler level,
but it is important that a processing framework such as Spring Batch
provide a way to return a numeric representation of the 'Exit Code'
for a particular batch job. In Spring Batch this is encapsulated
within an <classname>ExitStatus</classname>, which is covered in more
detail in Chapter 5. For the purposes of discussing exit codes, the
only important thing to know is that an
<classname>ExitStatus</classname> has an exit code property that is
set by the framework (or the developer) and is returned as part of the
<classname>JobExecution</classname> returned from the
@@ -554,7 +568,7 @@
it will be injected into the runner after the context is created. All
that needs to be done to provide your own
<classname>ExitCodeMapper</classname> is to declare the implementation
as a root level bean, and ensure it's part of the
as a root level bean and ensure that it is part of the
<classname>ApplicationContext</classname> that is loaded by the
runner.</para>
</section>
@@ -589,12 +603,12 @@
<para>A <classname>JobLauncher</classname> uses the
<classname>JobRepository</classname> to create new
<classname>JobExecution</classname> objects, and run them.
<classname>JobExecution</classname> objects and run them.
<classname>Job</classname> and <classname>Step</classname> implementations
later use the same <classname>JobRepository</classname> for basic updates
of the same executions during the running of a <classname>Job</classname>.
The basic operations suffice for simple scenarios. However, in a large
batch environment with hundreds of batch jobs and complex scheduling
The basic operations suffice for simple scenarios, but in a large batch
environment with hundreds of batch jobs and complex scheduling
requirements, more advanced access of the meta data is required:</para>
<mediaobject>
@@ -654,9 +668,10 @@
<para><link linkend="repositoryTablePrefix">Earlier in this
chapter</link>, it was mentioned that the table prefix of the
JobRepository can be modified to allow for different versions or
schemas. Because the JobExplorer is working with the same tables, it too
needs the ability to set a prefix:</para>
<classname>JobRepository</classname> can be modified to allow for
different versions or schemas. Because the
<classname>JobExplorer</classname> is working with the same tables, it
too needs the ability to set a prefix:</para>
<programlisting>
&lt;bean id="jobExplorer" class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean"
@@ -741,35 +756,43 @@
self-explanatory, and more detailed explanations can be found on the
<ulink
url="http://static.springframework.org/spring-batch/apidocs/org/springframework/batch/core/launch/JobOperator.html">javadoc
of the interface</ulink>. However, the 'startNextInstance' method is
worth noting. This method will always start a new instance of a Job.
of the interface</ulink>. However, the
<methodname>startNextInstance</methodname> method is worth noting. This
method will always start a new instance of a <classname>Job</classname>.
This can be extremely useful if there are serious issues in a
<classname>Job</classname>Execution, and the <classname>Job</classname>
<classname>JobExecution</classname> and the <classname>Job</classname>
needs to be started over again from the beginning. Unlike
<classname>JobLauncher</classname> though, which requires a new
<classname>JobParameters</classname> that will trigger a new JobInstance
if they are different than any previous one, the startNextInstance
method will use the JobParametersIncrementer tied to the Job to force
the <classname>Job</classname> to a new instance:</para>
<classname>JobParameters</classname> object that will trigger a new
<classname>JobInstance</classname> if the parameters are different from
any previous set of parameters, the
<methodname>startNextInstance</methodname> method will use the
<classname>JobParametersIncrementer</classname> tied to the
<classname>Job</classname> to force the <classname>Job</classname> to a
new instance:</para>
<programlisting>
public interface JobParametersIncrementer {
JobParameters getNext(JobParameters parameters);
}
</programlisting>
<para>The contract of <classname>JobParametersIncrementer</classname> is
that, given a <link linkend="jobParameters">JobParameters</link>, it
will return the 'next' parameter by incrementing any values it may
contain. This strategy is useful because the framework has no way of
knowing what changes to the JobParameters make it the 'next' instance.
For example, if the only value in JobParameters is a date, and the next
instance should be created, should that value be incremented by one day?
Or one week? (if the job is weekly for instance) The same can be said
for any numerical values that help to identify the Job, as shown
below:</para>
that, given a <link
linkend="jobParameters"><classname>JobParameters</classname></link>
object, it will return the 'next' <classname>JobParameters</classname>
object by incrementing any necessary values it may contain. This
strategy is useful because the framework has no way of knowing what
changes to the <classname>JobParameters</classname> make it the 'next'
instance. For example, if the only value in
<classname>JobParameters</classname> is a date, and the next instance
should be created, should that value be incremented by one day? Or one
week (if the job is weekly for instance)? The same can be said for any
numerical values that help to identify the <classname>Job</classname>,
as shown below:</para>
<programlisting>
public class SampleIncrementer implements JobParametersIncrementer {
@@ -786,12 +809,13 @@
</programlisting>
<para>In this example, the value with a key of 'run.id' is used to
discriminate between JobInstances. If the JobParameters passed in is
null, it can be assumed that the Job has never been run before and thus
it's initial state can be returned. However, if not, the old value is
obtained, incremented by one, and returned. An incrementer can be
associated with Job via the 'incrementer' attribute in the
namespace:</para>
discriminate between <classname>JobInstances</classname>. If the
<classname>JobParameters</classname> passed in is null, it can be
assumed that the <classname>Job</classname> has never been run before
and thus its initial state can be returned. However, if not, the old
value is obtained, incremented by one, and returned. An incrementer can
be associated with <classname>Job</classname> via the 'incrementer'
attribute in the namespace:</para>
<programlisting>
&lt;job id="footballJob" <emphasis role="bold">incrementer="sampleIncrementer"</emphasis>&gt;
@@ -819,8 +843,8 @@
<para>The shutdown is not immediate, since there is no way to force
immediate shutdown, especially if the execution is currently in
developer code that the framework has no control over, such as a
business service. What it does mean, is that as soon as control is
returned back to the framework, it will set the status of the current
business service. However, as soon as control is returned back to the
framework, it will set the status of the current
<classname>StepExecution</classname> to
<classname>BatchStatus.STOPPED</classname>, save it, then do the same
for the <classname>JobExecution</classname> before finishing.</para>

View File

@@ -477,7 +477,7 @@
processing locations and vice versa. File moving utilities are beyond
the scope of the spring batch architecture but it is not unusual for
batch job streams to include file moving utilities as steps in the job
stream. Its sufficient that the batch architecture only needs to know
stream. It is sufficient that the batch architecture only needs to know
how to locate the files to be processed. Spring Batch begins the process
of feeding the data into the pipe from this starting point. However,
<ulink
@@ -800,7 +800,7 @@
<programlisting>
FlatFileItemReader&lt;Player&gt; itemReader = new FlatFileItemReader&lt;Player&gt;();
itemReader.setResource(new FileSystemResource("resources/players.csv"));
//DelimitedLineTokenizer defaults to comma as it's delimiter
//DelimitedLineTokenizer defaults to comma as its delimiter
LineMapper&lt;Player&gt; lineMapper = new DefaultLineMapper&lt;Player&gt;();
lineMapper.setLineTokenizer(new DelimitedLineTokenizer());
lineMapper.setFieldSetMapper(new PlayerFieldSetMapper());
@@ -973,7 +973,7 @@
<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
have the same format. However, this may not always be the case. Its
have the same format. However, this may not always be the case. It is
very common that a file might have records with different formats that
need to be tokenized differently and mapped to different objects. The
following excerpt from a file illustrates this:</para>
@@ -1219,7 +1219,7 @@
<title>Simplified File Writing Example</title>
<para>Now that the <classname>LineAggregator</classname> interface and
it's most basic implementation,
its most basic implementation,
<classname>PassThroughLineAggregator</classname>, have been defined,
the basic flow of writing can be explained:</para>
@@ -2408,9 +2408,9 @@
existing service needs to act as an <classname>ItemReader</classname> or
<classname>ItemWriter</classname>, either to satisfy the dependency of
another Spring Batch class, or because it truly is the main
<classname>ItemReader</classname> for a step. Its fairly trivial to write
an adaptor class for each service that needs wrapping, but because its
such a common concern, Spring Batch provides implementations:
<classname>ItemReader</classname> for a step. It is fairly trivial to
write an adaptor class for each service that needs wrapping, but because
it is such a common concern, Spring Batch provides implementations:
<classname>ItemReaderAdapter</classname> and
<classname>ItemWriterAdapter</classname>. Both classes implement the
standard Spring method invoking the delegate pattern and are fairly simple

View File

@@ -16,7 +16,7 @@
respectively. <classname>ExecutionContext</classname> maps to both
BATCH_JOB_EXECUTION_CONTEXT and BATCH_STEP_EXECUTION_CONTEXT. The
<classname>JobRepository</classname> is responsible for saving and storing
each Java object into it's correct table. The following appendix describes
each Java object into its correct table. The following appendix describes
the meta-data tables in detail, along with many of the design decisions
that were made when creating them. When viewing the various table creation
statements below, it is important to realize that the data types used are
@@ -49,7 +49,7 @@
to try and save the value, if the version number has change it will
throw <classname>OptimisticLockingFailureException</classname>,
indicating there has been an error with concurrent access. This check is
necessary, since even though different batch jobs may be running in
necessary since, even though different batch jobs may be running in
different machines, they are all using the same database tables.</para>
</section>
@@ -57,11 +57,11 @@
<title>Identity</title>
<para>BATCH_JOB_INSTANCE, BATCH_JOB_EXECUTION, and BATCH_STEP_EXECUTION
each contain columns ending in _ID, which act as primary keys for their
respective tables. However, they are not database generated keys, but
rather are generated by separate sequences. This is necessary because
after inserting one of the domain objects into the database, the key it
is given needs to be set on the actual object, so that they can be
each contain columns ending in _ID. These fields act as primary keys for
their respective tables. However, they are not database generated keys,
but rather they are generated by separate sequences. This is necessary
because after inserting one of the domain objects into the database, the
key it is given needs to be set on the actual object so that they can be
uniquely identified in Java. Newer database drivers (Jdbc 3.0 and up)
support this feature with database generated keys, but rather than
requiring it, sequences were used. Each variation of the schema will
@@ -431,8 +431,8 @@ INSERT INTO BATCH_JOB_SEQ values(0);</programlisting>
<para>The BATCH_JOB_EXECUTION_CONTEXT table holds all information relevant
to an <classname>Job</classname>'s
<classname>ExecutionContext</classname>. There is exactly one
<classname>ExecutionContext</classname> per
<classname>StepExecution</classname>, and it contains all of the job-level
<classname>Job</classname> <classname>ExecutionContext</classname> per
<classname>JobExecution</classname>, and it contains all of the job-level
data that is needed for a particular job execution. This data typically
represents the state that must be retrieved after a failure so that a
<classname>JobInstance</classname> can 'start from where it left