copy samples docos over from trunk
This commit is contained in:
@@ -46,37 +46,45 @@ Spring Batch Samples
|
||||
Here is a list of samples with checks to indicate which features each one demonstrates:
|
||||
|
||||
*----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
|
||||
|<<Job / Feature>> | <<delimited input>> | <<fixed-length input>> | <<xml input>> | <<multiline input>> | <<db driving query input>> | <<db cursor input>> | <<delimited output>> | <<fixed-length output>> | <<xml output>> | <<multiline output>> | <<db output>> | <<skip>> | <<restart>> | <<automatic mapping>> | <<JMX>> | <<validation>> | <<delegation>>
|
||||
|<<Job / Feature>> | <<delimited input>> | <<fixed-length input>> | <<xml input>> | <<multiline input>> | <<db driving query input>> | <<db cursor input>> | <<delimited output>> | <<fixed-length output>> | <<xml output>> | <<multiline output>> | <<db output>> | <<skip>> | <<restart>> | <<automatic mapping>> | <<asynch launch>> | <<validation>> | <<delegation>> | <<write behind>> |
|
||||
*----
|
||||
adhocLoopJob | | | | | | | | | | | | | | | x | |
|
||||
{{adhocLoop}} | | | | | | | | | | | | | | |x | | | |
|
||||
*----
|
||||
beanWrapperMapperSample | | x | | | | | | | | | x | | | x | | x |
|
||||
{{batchUpdate}} | | | | | |x | | | | | | | | | | | |x |
|
||||
*----
|
||||
compositeProcessorSample | | | | | | | | x | | | x | | | | | x |
|
||||
{{beanWrapperMapperSample}} | |x | | | | | | | | |x | | |x | |x | | |
|
||||
*----
|
||||
delegatingJob | | | | | | | | | | | | | | | | | x
|
||||
{{compositeItemWriterSample}}| | | | | | | |x | | |x | | | | |x | | |
|
||||
*----
|
||||
fixedLengthImportJob | | x | | | | | | | | | x | | | | | x |
|
||||
{{delegating}} | | | | | | | | | | | | | | | | |x | |
|
||||
*----
|
||||
hibernateJob | | | | | | x | | | | | x | | | | | |
|
||||
{{fixedLengthImport}} | |x | | | | | | | | |x | | | | |x | | |
|
||||
*----
|
||||
ibatisJob | | | | | x | | | | | | x | | | | | |
|
||||
{{football}} |x | | | | |x | | | | |x | | | | | | | |
|
||||
*----
|
||||
infiniteLoopJob | | | | | | | | | | | | | | | | |
|
||||
{{hibernate}} | | | | | |x | | | | |x | | | | | | |x |
|
||||
*----
|
||||
multilineJob | | x | | x | | | | | | | | | | | | |
|
||||
{{ibatis}} | | | | |x | | | | | |x | | | | | | | |
|
||||
*----
|
||||
multilineOrderJob | x | | | x | | | | x | | x | | | | | | |
|
||||
{{multiline}} | |x | |x | | | | | | | | | | | | | | |
|
||||
*----
|
||||
fotballlJob | x | | | | | x | | | | | x | | | | | |
|
||||
{{multilineOrder}} |x | | |x | | | |x | |x | | | | | | | | |
|
||||
*----
|
||||
restartSample | | x | | | | | | | | | x | | x | | | x |
|
||||
{{parallel}} | |x | | | | | | | | |x | | | | |x | | |
|
||||
*----
|
||||
simpleTaskletJob | | | | | | | | | | | | | | | | |
|
||||
{{quartzSample}} |x | | | | |x | | | | |x | | | |x | | | |
|
||||
*----
|
||||
tradeJob | x | | | | x | | x | | | | x | | | | | x |
|
||||
{{restartSample}} | |x | | | | | | | | |x | |x | | |x | | |
|
||||
*----
|
||||
xmlStaxJob | | | x | | | | | | x | | | | | | | |
|
||||
{{retrySample}} | | | | | | | | | | | | | | | |x | | |
|
||||
*----
|
||||
{{skipSample}} |x | | | |x | |x | | | |x |x | | | |x | | |
|
||||
*----
|
||||
{{tasklet}} | | | | | | | | | | | | | | | | | | |
|
||||
*----
|
||||
{{trade}} |x | | | |x | |x | | | |x | | | | |x | | |
|
||||
*----
|
||||
{{xmlStax}} | | |x | | | | | |x | | | | | | | | | |
|
||||
*----
|
||||
|
||||
* Common Sample Source Structures
|
||||
@@ -102,84 +110,93 @@ xmlStaxJob | | | x | | | | | | x | | | | | | | |
|
||||
Each job consists of several steps, these steps are defined in steps
|
||||
property.
|
||||
|
||||
** Tasklet Job
|
||||
* Adhoc Loop and JMX Demo ({adhocLoop})
|
||||
|
||||
The goal is to show the simplest use of the batch framework with a
|
||||
single job with a single step, which cleans up a directory and runs
|
||||
a system command.
|
||||
This job is simply an infinite loop. It runs forever so it is
|
||||
useful for testing features to do with stopping and starting jobs.
|
||||
It is used, for instance, as one of the jobs that can be run from
|
||||
JMX using the Eclipse launch configuration "jmxLauncher".
|
||||
|
||||
<Description:> This job is defined by <<<taskletJob.xml>>> file. The
|
||||
<<<Job>>> itself is defined by the bean definition with
|
||||
<<<id="taskletJob">>>. In this example we have two steps.
|
||||
The JMX launcher uses an additional XML configuration file
|
||||
(adhoc-job-launcher-context.xml) to set up a <<<JobLauncher>>> for
|
||||
running jobs asynchronously (i.e. in a background thread). This
|
||||
follows the same pattern as the {{{quartzSample}Quartz sample}}, so
|
||||
see that section for more details of the <<<JobLauncher>>>
|
||||
configuration.
|
||||
|
||||
* The first step defines a tasklet that is responsible for
|
||||
clearing out a directory though a custom <<<Tasklet>>>. Each
|
||||
tasklet has an <<<execute()>>> method which is called by the
|
||||
step. All processing of business data should be handled by this
|
||||
method.
|
||||
The rest of the configuration for this demo consists of exposing
|
||||
some components from the application context as JMX managed beans.
|
||||
The <<<JobLauncher>>> is exposed as a stripped down interface
|
||||
<<<ExportedJobLauncher>>>, so that it can be controlled from a
|
||||
remote client (such as JConsole from the JDK) which does not have
|
||||
Spring Batch on the classpath. See the Spring Core Reference Guide
|
||||
for more details on how to customise the JMX configuration.
|
||||
|
||||
* The second step uses another tasklet to execute a system (OS)
|
||||
command line.
|
||||
* Batch Update ({batchUpdate})
|
||||
|
||||
<XML definition:> taskletJob.xml
|
||||
The purpose of this sample is to show to usage of the
|
||||
<<<BatchSqlUpdateItemWriter>>> to make efficient updates to a
|
||||
database table.
|
||||
|
||||
You can visualize the Spring configuration of a job through
|
||||
Spring-IDE. See {{{http://springide.org/blog/}Spring IDE}}. The
|
||||
source view of the configuration is as follows:
|
||||
The <<<BatchSqlUpdateItemWriter>>> accepts a special form of
|
||||
<<<PreparedStatementSetter>>> as a (mandatory) dependency. This is
|
||||
responsible for copying fields from the item to be written to a
|
||||
<<<PreparedStatement>>> matching the SQL query that has been
|
||||
injected. The implementation of the
|
||||
<<<CustomerCreditUpdatePreparedStatementSetter>>> shows best
|
||||
practice of keeping all the information needed for the execution in
|
||||
one place, since it contains a static constant value (<<<QUERY>>>)
|
||||
which is used to configure the query for the writer.
|
||||
|
||||
+---
|
||||
<bean id="taskletJob" parent="simpleJob">
|
||||
<property name="steps">
|
||||
<list>
|
||||
<bean id="deleteFilesInDir" parent="taskletStep">
|
||||
<property name="tasklet">
|
||||
<bean
|
||||
class="org.springframework.batch.sample.tasklet.FileDeletingTasklet">
|
||||
<property name="directoryResource"
|
||||
ref="directory" />
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
<bean id="executeSystemCommand" parent="taskletStep">
|
||||
<property name="tasklet">
|
||||
<bean
|
||||
class="org.springframework.batch.sample.tasklet.SystemCommandTasklet">
|
||||
<property name="command" value="echo hello" />
|
||||
<!-- 5 second timeout for the command to complete -->
|
||||
<property name="timeout" value="5000" />
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</list>
|
||||
</property>
|
||||
* BeanWrapperMapper Sample ({beanWrapperMapperSample})
|
||||
|
||||
</bean>
|
||||
This sample shows the use of automatic mapping from fields in a file
|
||||
to a domain object. The <<<Trade>>> and <<<Person>>> objects needed
|
||||
by the job are created from the Spring configuration using prototype
|
||||
beans, and then their properties are set using the
|
||||
<<<BeanWrapperFieldSetMapper>>>, which sets properties of the
|
||||
prototype according to the field names in the file.
|
||||
|
||||
<bean id="directory"
|
||||
class="org.springframework.core.io.FileSystemResource">
|
||||
<constructor-arg value="target/test-outputs/test-dir" />
|
||||
</bean>
|
||||
+---
|
||||
Nested property paths are resolved in the same way as normal Spring
|
||||
binding occurs, but with a little extra leeway in terms of spelling
|
||||
and capitalisation. Thus for instance, the <<<Trade>>> object has a
|
||||
property called <<<customer>>> (lower case), but the file has been
|
||||
configured to have a column name <<<CUSTOMER>>> (upper case), and
|
||||
the mapper will accept the values happily. Underscores instead of
|
||||
camel-casing (e.g. <<<CREDIT_CARD>>> instead of <<<creditCard>>>)
|
||||
also work.
|
||||
|
||||
For simplicity we are only displaying the job configuration itself
|
||||
and leaving out the details of the supporting batch execution
|
||||
environment configuration.
|
||||
* Composite ItemWriter Sample ({compositeItemWriterSample})
|
||||
|
||||
** Fixed Length Import Job
|
||||
This shows a common use case using a composite pattern, composing
|
||||
instances of other framework readers or writers. It is also quite
|
||||
common for business-specific readers or writers to wrap
|
||||
off-the-shelf components in a similar way.
|
||||
|
||||
In this job the composite pattern is used just to make duplicate
|
||||
copies of the output data. The delegates for the
|
||||
<<<CompositeItemWriter>>> have to be separately registered as
|
||||
streams in the <<<Step>>> where they are used, in order for the step
|
||||
to be restartable. This is a common feature of all delegate
|
||||
patterns.
|
||||
|
||||
* Delegating Sample ({delegating})
|
||||
|
||||
This sample shows the delegate pattern again, and also the
|
||||
<<<ItemReaderAdapter>>> which is used to adapt a POJO to the
|
||||
<<<ItemReader>>> interface.
|
||||
|
||||
* Fixed Length Import Job ({fixedLengthImport})
|
||||
|
||||
The goal is to demonstrate a typical scenario of importing data
|
||||
from a fixed-length file to database
|
||||
|
||||
<Description:> This job shows a more typical scenario, when reading
|
||||
input data and processing the data is cleanly separated. The data
|
||||
provider is responsible for reading input and mapping each record to
|
||||
a domain object, which is then passed to the module processor. The
|
||||
module processor handles the processing of the domain objects, in
|
||||
this case it only writes them to database.
|
||||
|
||||
<XML definition:> fixedLengthImportJob.xml
|
||||
|
||||
<Input source:> file with fixed row structure
|
||||
This job shows a typical scenario, when reading input data and
|
||||
processing the data is cleanly separated. The data provider is
|
||||
responsible for reading input and mapping each record to a domain
|
||||
object, which is then passed to the module processor. The module
|
||||
processor handles the processing of the domain objects, in this case
|
||||
it only writes them to database.
|
||||
|
||||
In this example we are using a simple fixed length record structure
|
||||
that can be found in the project at
|
||||
@@ -214,109 +231,7 @@ xmlStaxJob | | | x | | | | | | x | | | | | | | |
|
||||
object
|
||||
|
||||
|
||||
* Multiline Order Job
|
||||
|
||||
The goal is to demostrate how to handle a more complex file input
|
||||
format, where a record meant for processing inludes nested records
|
||||
and spans multiple lines
|
||||
|
||||
<XML definition:> multilineOrderJob.xml
|
||||
|
||||
<Input source:> file with multiline records. OrderDataProvider is
|
||||
an example of a non-default programmatic data provider. It reads
|
||||
input until it detects that the multiline record has finished and
|
||||
encapsulates the record in a single domain object.
|
||||
|
||||
<Output target:> file with multiline records. The concrete
|
||||
<<<ItemWriter>>> passes the object to a an injected 'delegate
|
||||
writer' which in this case writes the output to a file. The writer
|
||||
in this case demonstrates how to write multiline output using a
|
||||
custom aggregator transformer.
|
||||
|
||||
* Quartz Sample
|
||||
|
||||
The goal is to demonstrate how to schedule job execution using
|
||||
Quartz scheduler. In this case there is no unit test to launch the
|
||||
sample because it just re-uses the football job. There is a main
|
||||
method in <<<QuartzBatchLauncher>>> and an Eclipse launch
|
||||
configuration which runs it with empty arguments. The main method
|
||||
is very basic - it is intended only as a guide to how Quartz might
|
||||
be used in principle.
|
||||
|
||||
<XML definition:> <<<quartz-job-launcher.xml>>>, also re-uses
|
||||
<<<footballJob.xml>>>
|
||||
|
||||
The configuration declares a <<<JobLauncher>>> bean. The launcher
|
||||
bean is different from the other samples only in that it uses an
|
||||
asynchronous task executor, so that the jobs are launched in a
|
||||
separate thread to the main method:
|
||||
|
||||
+---
|
||||
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
|
||||
<property name="jobRepository" ref="jobRepository" />
|
||||
<property name="taskExecutor">
|
||||
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor" />
|
||||
</property>
|
||||
</bean>
|
||||
+---
|
||||
|
||||
Also, a Quartz <<<JobDetail>>> is defined using a Spring
|
||||
<<<JobDetailBean>>> as a convenience.
|
||||
|
||||
+--
|
||||
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
|
||||
<property name="triggers">
|
||||
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
|
||||
<property name="jobDetail" ref="jobDetail" />
|
||||
<property name="cronExpression" value="0/10 * * * * ?" />
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
+--
|
||||
|
||||
Finally, a trigger with a scheduler is defined that will launch the
|
||||
job detail every 10 seconds:
|
||||
|
||||
+---
|
||||
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
|
||||
<property name="triggers">
|
||||
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
|
||||
<property name="jobDetail" ref="jobDetail" />
|
||||
<property name="cronExpression" value="0/10 * * * * ?" />
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
+---
|
||||
|
||||
The job is thus scheduled to run every 10 seconds. In fact it
|
||||
should be successful on the first attempt, so the second and
|
||||
subsequent attempts should through a
|
||||
<<<JobInstanceAlreadyCompleteException>>>. In a production system,
|
||||
the job detail would probably be modified to account for this
|
||||
exception (e.g. catch it and re-submit with a new set of job
|
||||
parameters). The point here is that Spring Batch guarantees that
|
||||
the job execution is idempotent - you can never inadvertently
|
||||
process the same data twice.
|
||||
|
||||
* Trade Job
|
||||
|
||||
The goal is to show a reasonably complex scenario, that would
|
||||
resemble the real-life usage of the framework.
|
||||
|
||||
<Description:> This job has 3 steps. First, data about trades are
|
||||
imported from a file to database. Second, the trades are read from
|
||||
the database and credit on customer accounts is decreased
|
||||
appropriately. Last, a report about customers is exported to a file.
|
||||
|
||||
<XML definition:> <<<tradeJob.xml>>> - the job definition,
|
||||
<<<tradeJobIo.xml>>> - input and output configuration
|
||||
|
||||
<Description:> This job has 3 steps. First, data about trades is
|
||||
imported from a file to database. Second, the data about trades is
|
||||
read from the database and credit on customer accounts is decreased
|
||||
appropriately. Last, a report about customers is exported to a file.
|
||||
|
||||
* Football Job
|
||||
* Football Job ({football})
|
||||
|
||||
This is a (American) Football statistics loading job. We gave it the
|
||||
id of <<<footballJob>>> in our configuration file. Before diving
|
||||
@@ -363,10 +278,10 @@ AbduKa00,1996,mia,15,nyg,0,0,0,0,0,17,96,,14,0
|
||||
receptions, rushes, and total touchdowns.
|
||||
|
||||
Our example batch job is going to load both files into a database,
|
||||
and then combine each to summarize how each player performed for a
|
||||
and then combine each to summarise how each player performed for a
|
||||
particular year. Although this example is fairly trivial, it shows
|
||||
multiple types of input, and the general style is a common batch
|
||||
scenario. That is, summarizing a very large dataset so that it can
|
||||
scenario. That is, summarising a very large dataset so that it can
|
||||
be more easily manipulated or viewed by an online web-based
|
||||
application. In an enterprise solution the third step, the reporting
|
||||
step, could be implemented through the use of Eclipse BIRT or one of
|
||||
@@ -425,10 +340,10 @@ AbduKa00,1996,mia,15,nyg,0,0,0,0,0,17,96,,14,0
|
||||
ref="playerFileItemReader" />
|
||||
<property name="itemWriter">
|
||||
<bean
|
||||
class="org.springframework.batch.sample.item.writer.PlayerItemWriter">
|
||||
class="org.springframework.batch.sample.domain.football.internal.internal.PlayerItemWriter">
|
||||
<property name="playerDao">
|
||||
<bean
|
||||
class="org.springframework.batch.sample.dao.JdbcPlayerDao">
|
||||
class="org.springframework.batch.sample.domain.football.internal.internal.JdbcPlayerDao">
|
||||
<property name="dataSource"
|
||||
ref="dataSource" />
|
||||
</bean>
|
||||
@@ -448,7 +363,7 @@ AbduKa00,1996,mia,15,nyg,0,0,0,0,0,17,96,,14,0
|
||||
the developer can remain solely concerned with their business
|
||||
logic.
|
||||
|
||||
* <ItemReaderr> – the item reader is the source of the information
|
||||
* <ItemReader> – the item reader is the source of the information
|
||||
pipe. At the most basic level input is read in from an input
|
||||
source, parsed into a domain object and returned. In this way, the
|
||||
good batch architecture practice of ensuring all data has been
|
||||
@@ -486,7 +401,7 @@ AbduKa00,1996,mia,15,nyg,0,0,0,0,0,17,96,,14,0
|
||||
Following the general flow of the batch job, the next step is to
|
||||
describe how each line of the file will be parsed from its string
|
||||
representation into a domain object. The first thing the provider
|
||||
will need is an InputSource, which is provided as part of the Spring
|
||||
will need is an <<<ItemReader>>>, which is provided as part of the Spring
|
||||
Batch infrastructure. Because the input is flat-file based, a
|
||||
<<<FlatFileItemReader>>> is used:
|
||||
|
||||
@@ -504,7 +419,7 @@ AbduKa00,1996,mia,15,nyg,0,0,0,0,0,17,96,,14,0
|
||||
</property>
|
||||
<property name="fieldSetMapper">
|
||||
<bean
|
||||
class="org.springframework.batch.sample.mapping.PlayerFieldSetMapper" />
|
||||
class="org.springframework.batch.sample.domain.football.internal.internal.PlayerFieldSetMapper" />
|
||||
</property>
|
||||
</bean>
|
||||
+---
|
||||
@@ -525,7 +440,7 @@ AbduKa00,1996,mia,15,nyg,0,0,0,0,0,17,96,,14,0
|
||||
<<<FieldSetMapper>>> interface. There is a single method,
|
||||
<<<mapLine()>>>, which maps <<<FieldSet>>>s the same way that
|
||||
developers are comfortable mapping <<<ResultSet>>>s into Java
|
||||
<<<Object>>>s, either by index or fieldname. This behavior is by
|
||||
<<<Object>>>s, either by index or field name. This behaviour is by
|
||||
intention and design similar to the <<<RowMapper>>> passed into a
|
||||
<<<JdbcTemplate>>>. You can see this below:
|
||||
|
||||
@@ -581,7 +496,7 @@ public class
|
||||
<property name="dataSource" ref="dataSource" />
|
||||
<property name="mapper">
|
||||
<bean
|
||||
class="org.springframework.batch.sample.mapping.PlayerSummaryMapper" />
|
||||
class="org.springframework.batch.sample.domain.football.internal.internal.PlayerSummaryMapper" />
|
||||
</property>
|
||||
<property name="sql">
|
||||
<value>
|
||||
@@ -596,7 +511,7 @@ games.player_id group by games.player_id, games.year_no
|
||||
</bean>
|
||||
+----
|
||||
|
||||
The SqlCursorInputSource has three dependences:
|
||||
The <<<JdbcCursorItemReader>>> has three dependences:
|
||||
|
||||
* A <<<DataSource>>>
|
||||
|
||||
@@ -613,5 +528,375 @@ games.player_id group by games.player_id, games.year_no
|
||||
sample application you can execute the JUnit test
|
||||
<<<FootballJobFunctionalTests>>>, and you'll see an output showing
|
||||
each of the records as they are processed. Please keep in mind that
|
||||
AoP is used to wrap the <<<ItemWriter>> and output each record as it
|
||||
AoP is used to wrap the <<<ItemWriter>>> and output each record as it
|
||||
is processed to the logger, which may impact performance.
|
||||
|
||||
* Hibernate {hibernate}
|
||||
|
||||
The purpose of this sample is to show a typical usage of Hibernate
|
||||
as an ORM tool in the input and output of a job.
|
||||
|
||||
The job uses a <<<HibernateCursorItemReader>>> for the input, where
|
||||
a simple HQL query is used to supply items. It also uses a
|
||||
non-framework <<<ItemWriter>>> wrapping a DAO, which perhaps was
|
||||
written as part of an online system.
|
||||
|
||||
|
||||
The output reliability and robustness are improved by the use of the
|
||||
<<<HibernateAwareItemWriter>>> from the framework. One of its roles
|
||||
is to buffer items and flush them explicitly, rather than implicitly
|
||||
on a transaction boundary (which would be the default). This
|
||||
"write-behind" behaviour is provided by Hibernate implicitly, but we
|
||||
need to take control of it so that the skip and retry features
|
||||
provided by Spring Batch can work effectively. Thus the other role
|
||||
of the <<<HibernateAwareItemWriter>>> is to watch out for failures
|
||||
and flush aggressively when an item is seen from a previously failed
|
||||
chunk. In this way there will always be a failure at some point
|
||||
immediately after the bad item was written, and the item is then
|
||||
easily identifiable.
|
||||
|
||||
* Ibatis ({ibatis})
|
||||
|
||||
The goal of this sample is to show the use of Ibatis as a query
|
||||
mapping tool. Its features are similar to the Hibernate sample, but
|
||||
it uses Ibatis to drive its input and output.
|
||||
|
||||
* Multiline ({multiline})
|
||||
|
||||
The goal of this sample is to show some common tricks with multiline
|
||||
records in file input jobs.
|
||||
|
||||
The input file in this case consists of two groups of trades
|
||||
delimited by special lines in a file (BEGIN and END):
|
||||
|
||||
+---
|
||||
BEGIN
|
||||
UK21341EAH4597898.34customer1
|
||||
UK21341EAH4611218.12customer2
|
||||
END
|
||||
BEGIN
|
||||
UK21341EAH4724512.78customer2
|
||||
UK21341EAH4810809.25customer3
|
||||
UK21341EAH4985423.39customer4
|
||||
END
|
||||
+---
|
||||
|
||||
The goal of the job is to operate on the two groups, so the item
|
||||
type is naturally <<<List<Trade>>>>. To get these items delivered
|
||||
from an item reader we employ two components from Spring Batch: the
|
||||
<<<AggregateItemReader>>> and the
|
||||
<<<PrefixMatchingCompositeLineTokenizer>>>. The latter is
|
||||
responsible for recognising the difference between the trade data
|
||||
and the delimiter records. The former is responsible for
|
||||
aggregating the trades from each group into a <<<List>>> and handing
|
||||
out the list from its <<<read()>>> method. To help these components
|
||||
perform their responsibilities we also provide some business
|
||||
knowledge about the data in the form of a <<<FieldSetMapper>>>
|
||||
(<<<TradeFieldSetMapper>>>). The <<<TradeFieldSetMapper>>> checks
|
||||
its input for the delimiter fields (BEGIN, END) and if it detects
|
||||
them, returns the special tokens that <<<AggregateItemReader>>>
|
||||
needs. Otherwise it maps the input into a <<<Trade>>> object.
|
||||
|
||||
* Multiline Order Job ({multilineOrder})
|
||||
|
||||
The goal is to demonstrate how to handle a more complex file input
|
||||
format, where a record meant for processing includes nested records
|
||||
and spans multiple lines
|
||||
|
||||
The input source is file with multiline records.
|
||||
<<<OrderItemReader>>> is an example of a non-default programmatic
|
||||
item reader. It reads input until it detects that the multiline
|
||||
record has finished and encapsulates the record in a single domain
|
||||
object.
|
||||
|
||||
The output target is a file with multiline records. The concrete
|
||||
<<<ItemWriter>>> passes the object to a an injected 'delegate
|
||||
writer' which in this case writes the output to a file. The writer
|
||||
in this case demonstrates how to write multiline output using a
|
||||
custom aggregator transformer.
|
||||
|
||||
* Parallel Sample ({parallel})
|
||||
|
||||
The purpose of this sample is to show multi-threaded step execution
|
||||
using the Process Indicator pattern.
|
||||
|
||||
The job reads data from the same file as the
|
||||
{{{fixedLengthImport}Fixed Length Import}} sample, but instead of
|
||||
writing it out directly it goes through a staging table, and the
|
||||
staging table is read in a multi-threaded step. Note that for such
|
||||
a simple example where the item processing was not expensive, there
|
||||
is unlikely to be much if any benefit in using a multi-threaded
|
||||
step.
|
||||
|
||||
Multi-threaded step execution is easy to configure using Spring
|
||||
Batch, but there are some limitations. Most of the out-of-the-box
|
||||
<<<ItemReader>>> and <<<ItemWriter>>> implementations are not
|
||||
designed to work in this scenario because they need to be
|
||||
restartable and they are also stateful. There should be no surprise
|
||||
about this, and reading a file (for instance) is usually fast enough
|
||||
that multi-threading that part of the process is not likely to
|
||||
provide much benefit, compared to the cost of managing the state.
|
||||
|
||||
The best strategy to cope with restart state from multiple
|
||||
concurrent threads depends on the kind of input source involved:
|
||||
|
||||
* For file-based input (and output) restart sate is practically
|
||||
impossible to manage. Spring Batch does not provide any features
|
||||
or samples to help with this use case.
|
||||
|
||||
* With message middleware input it is trivial to manage restarts,
|
||||
since there is no state to store (if a transaction rolls back the
|
||||
messages are returned to the destination they came from).
|
||||
|
||||
* With database input state management is still necessary, but it
|
||||
isn't particularly difficult. The easiest thing to do is rely on
|
||||
a Process Indicator in the input data, which is a column in the
|
||||
data indicating for each row if it has been processed or not. The
|
||||
flag is updated inside the batch transaction, and then in the case
|
||||
of a failure the updates are lost, and the records will show as
|
||||
un-processed on a restart.
|
||||
|
||||
This last strategy is implemented in the <<<StagingItemReader>>>.
|
||||
Its companion, the <<<StagingItemWriter>>> is responsible for
|
||||
setting up the data in a staging table which contains the process
|
||||
indicator. The reader is then driven by a simple SQL query that
|
||||
includes a where clause for the processed flag, i.e.
|
||||
|
||||
+---
|
||||
SELECT ID FROM BATCH_STAGING WHERE JOB_ID=? AND PROCESSED=? ORDER BY ID
|
||||
+---
|
||||
|
||||
It is then responsible for updating the processed flag (which
|
||||
happens inside the main step transaction).
|
||||
|
||||
* Quartz Sample ({quartz})
|
||||
|
||||
The goal is to demonstrate how to schedule job execution using
|
||||
Quartz scheduler. In this case there is no unit test to launch the
|
||||
sample because it just re-uses the football job. There is a main
|
||||
method in <<<JobRegistryBackgroundJobRunner>>> and an Eclipse launch
|
||||
configuration which runs it with arguments to pick up the football
|
||||
job.
|
||||
|
||||
The additional XML configuration for this job is in
|
||||
<<<quartz-job-launcher.xml>>>, and it also re-uses
|
||||
<<<footballJob.xml>>>
|
||||
|
||||
The configuration declares a <<<JobLauncher>>> bean. The launcher
|
||||
bean is different from the other samples only in that it uses an
|
||||
asynchronous task executor, so that the jobs are launched in a
|
||||
separate thread to the main method:
|
||||
|
||||
+---
|
||||
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
|
||||
<property name="jobRepository" ref="jobRepository" />
|
||||
<property name="taskExecutor">
|
||||
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor" />
|
||||
</property>
|
||||
</bean>
|
||||
+---
|
||||
|
||||
Also, a Quartz <<<JobDetail>>> is defined using a Spring
|
||||
<<<JobDetailBean>>> as a convenience.
|
||||
|
||||
+--
|
||||
<bean id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
|
||||
<property name="jobClass" value="org.springframework.batch.sample.quartz.JobLauncherDetails" />
|
||||
<property name="group" value="quartz-batch" />
|
||||
<property name="jobDataAsMap">
|
||||
<map>
|
||||
<entry key="jobName" value="footballJob"/>
|
||||
<entry key="jobLocator" value-ref="jobRegistry"/>
|
||||
<entry key="jobLauncher" value-ref="jobLauncher"/>
|
||||
</map>
|
||||
</property>
|
||||
</bean>
|
||||
+--
|
||||
|
||||
Finally, a trigger with a scheduler is defined that will launch the
|
||||
job detail every 10 seconds:
|
||||
|
||||
+---
|
||||
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
|
||||
<property name="triggers">
|
||||
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
|
||||
<property name="jobDetail" ref="jobDetail" />
|
||||
<property name="cronExpression" value="0/10 * * * * ?" />
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
+---
|
||||
|
||||
The job is thus scheduled to run every 10 seconds. In fact it
|
||||
should be successful on the first attempt, so the second and
|
||||
subsequent attempts should through a
|
||||
<<<JobInstanceAlreadyCompleteException>>>. In a production system,
|
||||
the job detail would probably be modified to account for this
|
||||
exception (e.g. catch it and re-submit with a new set of job
|
||||
parameters). The point here is that Spring Batch guarantees that
|
||||
the job execution is idempotent - you can never inadvertently
|
||||
process the same data twice.
|
||||
|
||||
* Restart Sample ({restartSample})
|
||||
|
||||
The goal of this sample is to show how a job can be restarted after
|
||||
a failure and continue processing where it left off.
|
||||
|
||||
To simulate a failure we "fake" a failure on the fourth record
|
||||
though the use of a sample component
|
||||
<<<ExceptionThrowingItemReaderProxy>>>. This is a stateful reader
|
||||
that counts how many records it has processed and throws a planned
|
||||
exception in a specified place. Since we re-use the same instance
|
||||
when we restart the job it will not fail the second time.
|
||||
|
||||
* Retry Sample ({retrySample})
|
||||
|
||||
The purpose of this sample is to show how to use the automatic retry
|
||||
capabilities of Spring Batch.
|
||||
|
||||
The retry is configured in the step through the
|
||||
<<<SkipLimitStepFactoryBean>>>:
|
||||
|
||||
+---
|
||||
<bean id="step1" parent="simpleStep"
|
||||
class="org.springframework.batch.core.step.item.SkipLimitStepFactoryBean">
|
||||
...
|
||||
<property name="retryLimit" value="3" />
|
||||
<property name="retryableExceptionClasses" value="java.lang.Exception" />
|
||||
</bean>
|
||||
+---
|
||||
|
||||
Failed items will cause a rollback for all <<<Exception>>> types, up
|
||||
to a limit of 3 attempts. On the 4th attempt, the failed item would
|
||||
be skipped, and there would be a callback to a
|
||||
<<<ItemSkipListener>>> if one was provided (via the "listeners"
|
||||
property of the step factory bean).
|
||||
|
||||
An <<<ItemReader>>> is provided that will generate unique
|
||||
<<<Trade>>> data by just incrementing a counter. Note that it uses
|
||||
the counter in its <<<mark()>>> and <<<reset()>>> methods so that
|
||||
the same content is returned after a rollback. The same content is
|
||||
returned, but the instance of <<<Trade>>> is different, which means
|
||||
that the implementation of <<<equals()>>> in the <<<Trade>>> object
|
||||
is important. This is because to identify a failed item on retry
|
||||
(so that the number of attempts can be counted) the framework by
|
||||
default uses <<<Object.equals()>>> to compare the recently failed
|
||||
item with a cache of previously failed items. Without implementing
|
||||
a field-based <<<equals()>>> method for the domain object, our job
|
||||
will spin round the retry for potentially quite a long time before
|
||||
failing because the default implementation of <<<equals()>>> is
|
||||
based on object reference, not on field content.
|
||||
|
||||
* Skip Sample ({skipSample})
|
||||
|
||||
The purpose of this sample is to show how to use the skip features
|
||||
of Spring Batch. Since skip is really just a special case of retry
|
||||
(with limit 0), the details are quite similar to the {{{retrySample}Retry
|
||||
Sample}}, but the use case is less artificial, since it
|
||||
is based on the {{{trade}Trade Sample}}.
|
||||
|
||||
The failure condition is still artificial, since it is triggered by
|
||||
a special <<<ItemWriter>>> wrapper (<<<ItemTrackingItemWriter>>>).
|
||||
The plan is that a certain item (the third) will fail business
|
||||
validation in the writer, and the system can then respond by
|
||||
skipping it. We also configure the step so that it will not roll
|
||||
back on the validation exception, since we know that it didn't
|
||||
invalidate the transaction, only the item. This is done through the
|
||||
transaction attribute:
|
||||
|
||||
+---
|
||||
<bean id="step2" parent="skipLimitStep">
|
||||
<property name="skipLimit" value="1" />
|
||||
<!-- No rollback for exceptions that are marked with "+" in the tx attributes -->
|
||||
<property name="transactionAttribute"
|
||||
value="+org.springframework.batch.item.validator.ValidationException" />
|
||||
....
|
||||
</bean>
|
||||
+---
|
||||
|
||||
The format for the transaction attribute specification is given in
|
||||
the Spring Core documentation (e.g. see the Javadocs for
|
||||
{{{http://static.springframework.org/spring/docs/2.5.x/api/org/springframework/transaction/interceptor/TransactionAttributeEditor.html}TransactionAttributeEditor}}).
|
||||
|
||||
* Tasklet Job ({tasklet})
|
||||
|
||||
The goal is to show the simplest use of the batch framework with a
|
||||
single job with a single step, which cleans up a directory and runs
|
||||
a system command.
|
||||
|
||||
<Description:> The
|
||||
<<<Job>>> itself is defined by the bean definition with
|
||||
<<<id="taskletJob">>>. In this example we have two steps.
|
||||
|
||||
* The first step defines a tasklet that is responsible for
|
||||
clearing out a directory though a custom <<<Tasklet>>>. Each
|
||||
tasklet has an <<<execute()>>> method which is called by the
|
||||
step. All processing of business data should be handled by this
|
||||
method.
|
||||
|
||||
* The second step uses another tasklet to execute a system (OS)
|
||||
command line.
|
||||
|
||||
You can visualise the Spring configuration of a job through
|
||||
Spring-IDE. See {{{http://springide.org/blog/}Spring IDE}}. The
|
||||
source view of the configuration is as follows:
|
||||
|
||||
+---
|
||||
<bean id="taskletJob" parent="simpleJob">
|
||||
<property name="steps">
|
||||
<list>
|
||||
<bean id="deleteFilesInDir" parent="taskletStep">
|
||||
<property name="tasklet">
|
||||
<bean
|
||||
class="org.springframework.batch.sample.tasklet.FileDeletingTasklet">
|
||||
<property name="directoryResource"
|
||||
ref="directory" />
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
<bean id="executeSystemCommand" parent="taskletStep">
|
||||
<property name="tasklet">
|
||||
<bean
|
||||
class="org.springframework.batch.sample.common.SystemCommandTasklet">
|
||||
<property name="command" value="echo hello" />
|
||||
<!-- 5 second timeout for the command to complete -->
|
||||
<property name="timeout" value="5000" />
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</list>
|
||||
</property>
|
||||
|
||||
</bean>
|
||||
|
||||
<bean id="directory"
|
||||
class="org.springframework.core.io.FileSystemResource">
|
||||
<constructor-arg value="target/test-outputs/test-dir" />
|
||||
</bean>
|
||||
+---
|
||||
|
||||
For simplicity we are only displaying the job configuration itself
|
||||
and leaving out the details of the supporting batch execution
|
||||
environment configuration.
|
||||
|
||||
* Trade Job ({trade})
|
||||
|
||||
The goal is to show a reasonably complex scenario, that would
|
||||
resemble the real-life usage of the framework.
|
||||
|
||||
This job has 3 steps. First, data about trades are imported from a
|
||||
file to database. Second, the trades are read from the database and
|
||||
credit on customer accounts is decreased appropriately. Last, a
|
||||
report about customers is exported to a file.
|
||||
|
||||
* XML Input Output ({xmlStax})
|
||||
|
||||
The goal here is to show the use of XML input and output through
|
||||
streaming and Spring OXM marshallers and unmarshallers.
|
||||
|
||||
The job has a single step that copies <<<Trade>>> data from one XML
|
||||
file to another. It uses XStream for the object XML conversion,
|
||||
because this is simple to configure for basic use cases like this
|
||||
one. See
|
||||
{{{http://static.springframework.org/spring-ws/sites/1.5/reference/html/oxm.html}Spring
|
||||
OXM documentation}} for details of other options.
|
||||
|
||||
Reference in New Issue
Block a user