diff --git a/docs/src/site/docbook/reference/core.xml b/docs/src/site/docbook/reference/core.xml
index 4f84173ce..297f0f780 100644
--- a/docs/src/site/docbook/reference/core.xml
+++ b/docs/src/site/docbook/reference/core.xml
@@ -963,11 +963,13 @@
public JobExecution createJobExecution(Job job, JobParameters jobParameters)
throws JobExecutionAlreadyRunningException, JobRestartException;
- void saveOrUpdate(JobExecution jobExecution);
+ void add(StepExecution stepExecution);
- void saveOrUpdate(StepExecution stepExecution);
+ void update(JobExecution jobExecution);
- void saveOrUpdateExecutionContext(StepExecution stepExecution);
+ void update(StepExecution stepExecution);
+
+ void updateExecutionContext(StepExecution stepExecution);
StepExecution getLastStepExecution(JobInstance jobInstance, Step step);
diff --git a/docs/src/site/docbook/reference/readersAndWriters.xml b/docs/src/site/docbook/reference/readersAndWriters.xml
index 44c61c650..d8befc122 100644
--- a/docs/src/site/docbook/reference/readersAndWriters.xml
+++ b/docs/src/site/docbook/reference/readersAndWriters.xml
@@ -50,15 +50,15 @@
ItemReader is a basic interface for generic
input operations:
- public interface ItemReader<T> {
- Object read() throws Exception;
+ T read() throws Exception, UnexpectedInputException, NoWorkFoundException, ParseException;
void mark() throws MarkFailedException;
void reset() throws ResetFailedException;
}
-]]>
+
The read method defines the most essential
contract of the ItemReader, calling it returns one
@@ -97,15 +97,15 @@
As with ItemReader,
ItemWriter is a fairly generic interface:
- public interface ItemWriter<T> {
- void write(Object item) throws Exception;
+ void write(T item) throws Exception;
void flush() throws FlushFailedException;
void clear() throws ClearFailedException;
}
-]]>
+
As with read on
ItemReader, write provides
@@ -135,7 +135,7 @@
readers and writers need to be opened, closed, and require a mechanism for
persisting state:
- public interface ItemStream {
void open(ExecutionContext executionContext) throws StreamException;
@@ -143,7 +143,7 @@
void close(ExecutionContext executionContext) throws StreamException;
}
-]]>
+
Before describing each method, its worth briefly mentioning the
ExecutionContext. Clients of an
@@ -203,11 +203,11 @@
fields so that the fields may be accessed either by index or name as
patterned after ResultSet:
- String[] tokens = new String[]{"foo", "1", "true"};
FieldSet fs = new DefaultFieldSet(tokens);
String name = fs.readString(0);
int value = fs.readInt(1);
- boolean booleanValue = fs.readBoolean(2);]]>
+ boolean booleanValue = fs.readBoolean(2);
There are many more options on the FieldSet
interface, such as Date, long,
@@ -240,9 +240,9 @@
Framework, Chapter 4.Resources. Therefore, this
guide will not go into the details of creating
Resource objects. However, a simple example of a
- file system resource can be found below:
Resource resource = new FileSystemResource("resources/trades.csv");
- ]]>
+
In complex batch environments the directory structures are often
managed by the EAI infrastructure where drop zones for external
@@ -336,11 +336,11 @@
LineTokenizer to translate a line of data from
a resource into an object of the desired type:
- public interface FieldSetMapper {
public Object mapLine(FieldSet fs);
- }]]>
+ }
The pattern used is the same as RowMapper
used by JdbcTemplate.
@@ -356,11 +356,11 @@
FieldSet is necessary. In Spring Batch, this is
called a LineTokenizer:
- public interface LineTokenizer {
FieldSet tokenize(String line);
- }]]>
+ }
The contract of a LineTokenizer is such
that, given a line of input (in theory the
@@ -420,14 +420,14 @@
In code, the above flow looks like the following:
- String line = readLine();
if (line != null) {
FieldSet tokenizedLine = tokenizer.tokenize(line);
return fieldSetMapper.mapLine(tokenizedLine);
}
- return null;]]>
+ return null;
Exception handling has been removed for clarity.
@@ -435,16 +435,16 @@
The following example will be used to illustrate this using an
actual domain scenario. This particular batch job reads in football
- players from the following file: ID,lastName,firstName,position,birthYear,debutYear
"AbduKa00,Abdul-Jabbar,Karim,rb,1974,1996",
"AbduRa00,Abdullah,Rabih,rb,1975,1999",
"AberWa00,Abercrombie,Walter,rb,1959,1982",
"AbraDa00,Abramowicz,Danny,wr,1945,1967",
"AdamBo00,Adams,Bob,te,1946,1969",
- "AdamCh00,Adams,Charlie,wr,1979,2003" ]]>
+ "AdamCh00,Adams,Charlie,wr,1979,2003"
The contents of this file will be mapped to the following Player
- domain object:
public class Player implements Serializable {
private String ID;
@@ -464,14 +464,14 @@
// setters and getters...
}
- ]]>
+
In order to map a FieldSet into a Player
object, a FieldSetMapper that returns players
needs to be defined:
-
+ protected static class PlayerFieldSetMapper implements FieldSetMapper<Player> {
public Object mapLine(FieldSet fieldSet) {
Player player = new Player();
@@ -484,22 +484,22 @@
return player;
}
- } ]]>
+ }
The file can then be read by correctly constructing a
FlatFileItemReader and calling
read:
-
+ FlatFileItemReader<Player> itemReader = new FlatFileItemReader<Player>();
itemReader.setResource(new FileSystemResource("resources/players.csv"));
//DelimitedLineTokenizer defaults to comma as it's delimiter
itemReader.setLineTokenizer(new DelimitedLineTokenizer());
itemReader.setFieldSetMapper(new PlayerFieldSetMapper());
itemReader.open(new ExecutionContext());
- Player player = (Player)itemReader.read();
+ Player player = itemReader.read();
-]]>
+
Each call to read will return a new
Player object from each line in the file. When the end of the file is
@@ -517,15 +517,15 @@
fields in the flat file are injected into the
LineTokenizer:
-
tokenizer.setNames(new String[] {"ID", "lastName","firstName","position","birthYear","debutYear"});
- ]]>
+
a FieldSetMapper can this use this
information as follows:
- public class PlayerMapper implements FieldSetMapper {
+ public class PlayerMapper implements FieldSetMapper<Player> {
public Object mapLine(FieldSet fs) {
if(fs == null){
@@ -559,14 +559,14 @@
FieldSetMapper configuration looks like the
following:
-
-
-
+ <bean id="fieldSetMapper"
+ class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
+ <property name="prototypeBeanName" value="player" />
+ </bean>
- ]]>
+ scope="prototype" />
For each entry in the FieldSet, the
mapper will look for a corresponding setter on a new instance of the
@@ -586,11 +586,11 @@
organizations that use flat files use fixed length formats. An example
fixed length file is below:
- UK21341EAH4121131.11customer1
UK21341EAH4221232.11customer2
UK21341EAH4321333.11customer3
UK21341EAH4421434.11customer4
- UK21341EAH4521535.11customer5]]>
+ UK21341EAH4521535.11customer5
While this looks like one large field, it actually represent 4
distinct fields:
@@ -620,14 +620,14 @@
FixedLengthLineTokenizer, each of these lengths
must be provided in the form of ranges:
-
-
-
-
+
+ <bean id="fixedLengthLineTokenizer"
+ class="org.springframework.batch.io.file.transform.FixedLengthTokenizer">
+ <property name="names" value="ISIN, Quantity, Price, Customer" />
+ <property name="columns" value="1-12, 13-15, 16-20, 21-29" />
+ </bean>
-]]>
+
This LineTokenizer will return the same
FieldSet as if a dlimiter had been used,
@@ -639,18 +639,18 @@
specialized property editor be configured anywhere in the
ApplicationContext:
-
-
-
-
-
+
+ <bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer">
+ <property name="customEditors">
+ <map>
+ <entry key="org.springframework.batch.item.file.transform.Range[]">
+ <bean class="org.springframework.batch.item.file.transform.RangeArrayPropertyEditor" />
+ </entry>
+ </map>
+ </property>
+ </bean>
-]]>
+
@@ -662,7 +662,7 @@
might have records spanning multiple lines with multiple formats. The
following excerpt from a file illustrates this:
- HEA;0013100345;2007-02-15
NCU;Smith;Peter;;T;20014539;F
BAD;;Oak Street 31/A;;Small Town;00235;IL;US
SAD;Smith, Elizabeth;Elm Street 17;;Some City;30011;FL;United States
@@ -670,30 +670,30 @@
LIT;1044391041;37.49;0;0;4.99;2.99;1;45.47
LIT;2134776319;221.99;5;0;7.99;2.99;1;221.87
SIN;UPS;EXP;DELIVER ONLY ON WEEKDAYS
- FOT;2;2;267.34]]>
+ FOT;2;2;267.34
Everything between the line starting with 'HEA' and the line
starting with 'FOT' is considered one record. The
PrefixMatchingCompositeLineTokenizer makes this easier by matching the
prefix in a line with a particular tokenizer:
-
-
-
-
- ]]>
+ <bean id="orderFileDescriptor"
+ class="org.springframework.batch.io.file.transform.PrefixMatchingCompositeLineTokenizer">
+ <property name="tokenizers">
+ <map>
+ <entry key="HEA" value-ref="headerRecordDescriptor" />
+ <entry key="FOT" value-ref="footerRecordDescriptor" />
+ <entry key="BCU" value-ref="businessCustomerLineDescriptor" />
+ <entry key="NCU" value-ref="customerLineDescriptor" />
+ <entry key="BAD" value-ref="billingAddressLineDescriptor" />
+ <entry key="SAD" value-ref="shippingAddressLineDescriptor" />
+ <entry key="BIN" value-ref="billingLineDescriptor" />
+ <entry key="SIN" value-ref="shippingLineDescriptor" />
+ <entry key="LIT" value-ref="itemLineDescriptor" />
+ <entry key="" value-ref="defaultLineDescriptor" />
+ </map>
+ </property>
+ </bean>
This ensures that the line will be parsed correctly, which is
especially important for fixed length input. Any users of the
@@ -716,70 +716,40 @@
LineAggregator
Just as the LineTokenizer interface is
- necessary to take a string and split it into tokens, file writing must
+ necessary to take an item and turn it into a string, file writing must
have a way to aggregate multiple fields into a single string for
writing to a file. In Spring Batch this is the
LineAggregator:
- public interface LineAggregator<T> {
- public String aggregate(FieldSet fieldSet);
+ public String aggregate(T item);
- }]]>
+ }
The LineAggregator is the opposite of a
LineTokenizer.
LineTokenizer takes a
String and returns a
FieldSet, whereas
- LineAggregator takes a
- FieldSet and returns a
+ LineAggregator takes an
+ item and returns a
String. As with reading there are two types:
DelimitedLineAggregator and
FixedLengthLineAggregator.
-
- FieldSetCreator
-
- Because the LineAggregator interface uses a
- FieldSet as it's mechanism for converting to a
- string, there needs to be an interface that describes how to convert
- from an object into a FieldSet:
-
-
-
- As with LineTokenizer and
- LineAggregator,
- FieldSetCreator is the polar opposite of
- FieldSetMapper.
- FieldSetMapper takes a
- FieldSet and returns a mapped object, whereas a
- FieldSetCreator takes an Object and returns a
- FieldSet.
-
-
Simple Delimited File Writing Example
- Now that both the LineAggregator and
- FieldSetCreator interfaces have been defined,
- the basic flow of writing can be explained:
+ Now that the LineAggregator interface has
+ been defined, the basic flow of writing can be explained:
The object to be written is passed to the
- FieldSetCreator in order to obtain a
- FieldSet.
-
-
-
- The returned FieldSet is passed to
- the LineAggregator
+ LineAggregator in order to obtain a
+ String.
@@ -792,22 +762,21 @@
FlatFileItemWriter expresses this in
code:
-
+ public void write(T item) throws Exception {
+ getOutputState().write(lineAggregator.aggregate(item) + LINE_SEPARATOR);
+ }
A simple configuration with the smallest ammount of setters
would look like the following:
-
-
-
-
-
- ]]>
+ <bean id="itemWriter"
+ class="org.springframework.batch.io.file.FlatFileItemWriter">
+ <property name="resource"
+ value="file:target/test-outputs/20070122.testStream.multilineStep.txt" />
+ <property name="lineAggregator">
+ <bean class="org.springframework.batch.item.file.transform.PassThroughLineAggregator"/>
+ </property>
+ </bean>
@@ -911,28 +880,28 @@
stream. First, lets examine a set of XML records that the
StaxEventItemReader can process.
-
-
-
- XYZ0001
- 5
- 11.39
- Customer1
-
-
- XYZ0002
- 2
- 72.99
- Customer2c
-
-
- XYZ0003
- 9
- 99.99
- Customer3
-
-]]>
+
+<?xml version="1.0" encoding="UTF-8"?>
+<records>
+ <trade xmlns="http://springframework.org/batch/sample/io/oxm/domain">
+ <isin>XYZ0001</isin>
+ <quantity>5</quantity>
+ <price>11.39</price>
+ <customer>Customer1</customer>
+ </trade>
+ <trade xmlns="http://springframework.org/batch/sample/io/oxm/domain">
+ <isin>XYZ0002</isin>
+ <quantity>2</quantity>
+ <price>72.99</price>
+ <customer>Customer2c</customer>
+ </trade>
+ <trade xmlns="http://springframework.org/batch/sample/io/oxm/domain">
+ <isin>XYZ0003</isin>
+ <quantity>9</quantity>
+ <price>99.99</price>
+ <customer>Customer3</customer>
+ </trade>
+</records>
To be able to process the XML records the following is needed:
@@ -954,22 +923,22 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ]]>
+ <property name="itemReader">
+ <bean class="org.springframework.batch.io.xml.StaxEventItemReader">
+ <property name="fragmentRootElementName" value="trade" />
+ <property name="resource" value="data/staxJob/input/20070918.testStream.xmlFileStep.xml" />
+ <property name="fragmentDeserializer">
+ <bean class="org.springframework.batch.io.xml.oxm.UnmarshallingEventReaderDeserializer">
+ <constructor-arg>
+ <bean class="org.springframework.oxm.xstream.XStreamMarshaller">
+ <property name="aliases" ref="aliases" />
+ </bean>
+ </constructor-arg>
+ </bean>
+ </property>
+ </bean>
+</property>
+
Notice that in this example we have chosen to use an
XStreamMarshaller that requires an alias passed
@@ -980,16 +949,16 @@
the map. In the configuration file we can use a spring configuration
utility to describe the required alias as follows:
-
-
-
-
-
-
-
- ]]>
+
+ <util:map id="aliases">
+ <entry key="trade"
+ value="org.springframework.batch.sample.domain.Trade" />
+ <entry key="isin" value="java.lang.String" />
+ <entry key="quantity" value="long" />
+ <entry key="price" value="java.math.BigDecimal" />
+ <entry key="customer" value="java.lang.String" />
+ </util:map>
+
On input the reader reads the XML resource until it recognizes a
new fragment is about to start (by matching the tag name by default).
@@ -1003,7 +972,7 @@
injection provided by the spring configuration would look something like
the following:
-
StaxEventItemReader xmlStaxEventItemReader = new StaxEventItemReader()
Resource resource = new ByteArrayResource(xmlResource.getBytes())
@@ -1031,7 +1000,7 @@
}
}
-]]>
+
@@ -1049,13 +1018,13 @@
MarshallingEventWriterSerializer. The Spring
configuration for this setup looks as follows:
-
-
-
-
-
-
-]]>
+ <bean class="org.springframework.batch.item.xml.StaxEventItemWriter" id="tradeStaxWriter">
+ <property name="resource"value="file:target/test-outputs/20070918.testStream.xmlFileStep.output.xml" />
+ <property name="serializer" ref="tradeMarshallingSerializer" />
+ <property name="rootTagName" value="trades" />
+ <property name="overwriteOutput" value="true" />
+</bean>
+
The configuration sets up the three required properties and
optionally sets the overwriteOutput=true, mentioned earlier in the
@@ -1075,7 +1044,7 @@
all of the points discussed, demonstrating the programmatic setup of the
required properties.
- StaxEventItemWriter staxItemWriter = new StaxEventItemWriter()
FileSystemResource resource = new FileSystemResource(File.createTempFile("StaxEventWriterOutputSourceTests", "xml"))
Map aliases = new HashMap();
@@ -1103,7 +1072,7 @@
trade.customer = "Customer1"
println trade
staxItemWriter.write(trade)
- staxItemWriter.flush()]]>
+ staxItemWriter.flush()
For a complete example configuration of XML input and output and a
corresponding Job see the sample xmlStaxJob.
@@ -1120,11 +1089,11 @@
java.io.File. Both XML and Flat File resources can
be configured using standard Spring constructs:
-
-
- ]]>
+ <bean id="flatFileItemReader"
+ class="org.springframework.batch.item.file.FlatFileItemReader">
+ <property name="resource"
+ value="file://outputs/20070122.testStream.CustomerReportStep.TEMP.txt" />
+ </bean>
The above Resource will load the file from
the file system, at the location specificied. Note that absolute locations
@@ -1134,10 +1103,10 @@
determined at runtime as a parameter to the job. This could be solved
using '-D' parameters, i.e. a system property:
-
-
-]]>
+ <bean id="flatFileItemReader"
+ class="org.springframework.batch.item.file.FlatFileItemReader">
+ <property name="resource" value="${input.file.name}" />
+</bean>
All that would be required for this solution to work would be a
system argument (-Dinput.file.name="file://file.txt"). (Note that although
@@ -1154,10 +1123,10 @@
either job name, step name, or any values from the
JobParameters, by surrounding them with %:
-
-
- ]]>
+ <bean id="inputFile"
+ class="org.springframework.batch.core.resource.StepExecutionResourceProxy" />
+ <property name="filePattern" value="//%JOB_NAME%/%STEP_NAME%/%file.name%" />
+ </bean>
Assuming a job name of 'fooJob', and a step name of 'fooStep', and
the key-value pair of 'file.name="fileName.txt"' is in the
@@ -1168,11 +1137,11 @@
StepExecution, it must be registered as a
StepListener:
- <bean id="fooStep" parent="abstractStep"
p:itemReader-ref="itemReader"
- p:itemWriter-ref="itemWriter">
-
- ]]>
+ p:itemWriter-ref="itemWriter">
+ <property name="listeners" ref="inputFile" />
+ </bean>
The StepListener interface will be discussed
in more detail in Chapter 4. For now, it is sufficient to know that the
@@ -1188,20 +1157,20 @@
for both XML and FlatFile processing. Consider the following files in a
directory:
-
+ file-1.txt file-2.txt ignored.txt
file-1.txt and file-2.txt are formatted the same and for business
reasons should be processed together. The
MuliResourceItemReader can be used to read in both
files by using wildcards:
-
-
-
-
+
+ <bean id="multiResourceReader" class="org.springframework.batch.item.SortedMultiResourceItemReader">
+ <property name="resources" value="classpath:data/multiResourceJob/input/file-*.txt" />
+ <property name="delegate" ref="flatFileItemReader" />
+ </bean>
-]]>
+
The referenced delegate is a simple
FlatFileItemReader. The above configuration will
@@ -1288,18 +1257,18 @@
DataSource. The following database schema will
be used as an example:
- CREATE TABLE CUSTOMER (
ID BIGINT IDENTITY PRIMARY KEY,
NAME VARCHAR(45),
CREDIT FLOAT
-);]]>
+);
Many people prefer to use a domain object for each row, so we'll
use an implementation of the RowMapper
interface to map a CustomerCredit
object:
- public class CustomerCreditRowMapper implements RowMapper {
public static final String ID_COLUMN = "id";
public static final String NAME_COLUMN = "name";
@@ -1315,7 +1284,7 @@
return customerCredit;
}
-}]]>
+}
Because JdbcTemplate is so familiar to
users of Spring, and the JdbcCursorItemReader
@@ -1326,12 +1295,12 @@
CUSTOMER database. The first example will be using
JdbcTemplate:
-
//For simplicity sake, assume a dataSource has already been obtained
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
List customerCredits = jdbcTemplate.query("SELECT ID, NAME, CREDIT from CUSTOMER", new CustomerCreditRowMapper());
-]]>
+
After running this code snippet the customerCredits list will
contain 1,000 CustomerCredit objects. In the
@@ -1342,7 +1311,7 @@
constrast this with the approach of the
JdbcCursorItemReader:
-
JdbcCursorItemReader itemReader = new JdbcCursorItemReader();
itemReader.setDataSource(dataSource);
itemReader.setSql("SELECT ID, NAME, CREDIT from CUSTOMER");
@@ -1357,7 +1326,7 @@
}
itemReader.close(executionContext);
-]]>
+
After running this code snippet the counter will equal 1,000. If
the code above had put the returned customerCredit into a list, the
@@ -1473,7 +1442,7 @@
configuration using the same 'customer credit' example as the JDBC
reader:
-
HibernateCursorItemReader itemReader = new HibernateCursorItemReader();
itemReader.setQueryString("from CustomerCredit");
//For simplicity sake, assume sessionFactory already obtained.
@@ -1489,7 +1458,7 @@
}
itemReader.close(executionContext);
-]]>
+
This configured ItemReader will return
CustomerCredit objects in the exact same manner
@@ -1534,13 +1503,13 @@
is an example configuration using the same 'customer credit' example
as the JDBC reader above:
-
-
-
-
-
-]]>
+ <bean id="itemReader"
+ class="org.springframework.batch.item.database.JpaPagingItemReader">
+ <property name="entityManagerFactory" ref="entityManagerFactory"/>
+ <property name="queryString" value="select c from CustomerCredit c"/>
+ <property name="pageSize" value="1000"/>
+ </bean>
+
This configured ItemReader will return
CustomerCredit objects in the exact same manner
@@ -1612,12 +1581,12 @@
real complication is how those keys are obtained. The
KeyCollector interface abstracts this:
- public interface KeyCollector {
List retrieveKeys(ExecutionContext executionContext);
void updateContext(Object key, ExecutionContext executionContext);
- }]]>
+ }
The primary method in this interface is the
retrieveKeys method. It is expected that this
@@ -1632,12 +1601,12 @@
retrieveKeys method can then use this value
to retrieve a subset of the original keys:
- ExecutionContext executionContext = new ExecutionContext();
List keys = keyStrategy.retrieveKeys(executionContext);
//Assume keys contains 1 through 1,000
keyStrategy.updateContext(new Long(500), executionContext);
keys = keyStrategy.retrieveKeys(executionContext);
- //keys should now contains 500 through 1,000]]>
+ //keys should now contains 500 through 1,000
This generalization illustrates the
KeyCollector contract. If we assume that
@@ -1700,36 +1669,36 @@
The following code helps illustrate how to setup and use a
SingleColumnJdbcKeyCollector:
- SingleColumnJdbcKeyCollector keyCollector = new SingleColumnJdbcKeyCollector(getJdbcTemplate(),
"SELECT ID from T_FOOS order by ID");
- keyCollector.setRestartSql("SELECT ID from T_FOOS where ID > ? order by ID");
+ keyCollector.setRestartSql("SELECT ID from T_FOOS where ID > ? order by ID");
ExecutionContext executionContext = new ExecutionContext();
List keys = keyStrategy.retrieveKeys(new ExecutionContext());
- for (int i = 0; i < keys.size(); i++) {
+ for (int i = 0; i < keys.size(); i++) {
System.out.println(keys.get(i));
- }]]>
+ }
If this code were run in the proper environment with the correct
database tables setup, then it would output the following:
- 1
2
3
4
-5]]>
+5
Now, let's modify the code slightly to show what would happen if
the code were started again after a restart, having failed after
processing key 3 successfully:
- SingleColumnJdbcKeyCollector keyCollector = new SingleColumnJdbcKeyCollector(getJdbcTemplate(),
"SELECT ID from T_FOOS order by ID");
- keyCollector.setRestartSql("SELECT ID from T_FOOS where ID > ? order by ID");
+ keyCollector.setRestartSql("SELECT ID from T_FOOS where ID > ? order by ID");
ExecutionContext executionContext = new ExecutionContext();
@@ -1737,19 +1706,19 @@
List keys = keyStrategy.retrieveKeys(executionContext);
- for (int i = 0; i < keys.size(); i++) {
+ for (int i = 0; i < keys.size(); i++) {
System.out.println(keys.get(i));
- }]]>
+ }
Running this code snippet would result in the following:
-
+ 4
+5
The key difference between the two examples is the following
line:
-
+ keyStrategy.updateContext(new Long(3), executionContext);
This tells the key collector to update the provided
ExecutionContext with the key of three. This
@@ -1760,7 +1729,7 @@
ExecutionContext that was updated to contain 3,
the argument of 3 will be passed to the restartSql:
- ? order by ID");]]>
+ keyCollector.setRestartSql("SELECT ID from T_FOOS where ID > ? order by ID");
This will cause only keys 4 and 5 to be returned, since they are
the only ones with an ID greater than 3.
@@ -1787,13 +1756,13 @@
An ExecutionContextRowMapper provides
this:
- public interface ExecutionContextRowMapper extends RowMapper {
public void mapKeys(Object key, ExecutionContext executionContext);
public PreparedStatementSetter createSetter(ExecutionContext executionContext);
}
-]]>
+
The ExecutionContextRowMapper interface
extends the standard RowMapper interface to
@@ -1891,36 +1860,36 @@
items to be skipped reliably. The following example illustrates how to
configure the HibernateAwareItemWriter:
-
-
-
-
+ <bean id="hibernateItemWriter"
+ class="org.springframework.batch.item.database.HibernateAwareItemWriter">
+ <property name="sessionFactory" ref="sessionFactory" />
+ <property name="delegate" ref="customerCreditWriter" />
+ </bean>
-
-
-
+ <bean id="customerCreditWriter"
+ class="org.springframework.batch.sample.dao.HibernateCreditDao">
+ <property name="sessionFactory" ref="sessionFactory" />
+ </bean>
-]]>
+
If you are using JPA then the
JpaAwareItemWriter provides comparable
functionality. The following example illustrates how to configure the
JpaAwareItemWriter:
-
-
-
-
+ <bean id="hibernateItemWriter"
+ class="org.springframework.batch.item.database.JpaAwareItemWriter">
+ <property name="entityManagerFactory" ref="entityManagerFactory" />
+ <property name="delegate" ref="customerCreditWriter" />
+ </bean>
-
-
-
+ <bean id="customerCreditWriter"
+ class="org.springframework.batch.sample.dao.JpaCreditDao">
+ <property name="entityManagerFactory" ref="entityManagerFactory" />
+ </bean>
-]]>
+
@@ -1945,12 +1914,12 @@
standard Spring method invoking delegator pattern and are fairly simple to
set up. Below is an example of the reader:
-
-
-
-
+ <bean id="itemReader" class="org.springframework.batch.item.adapter.ItemReaderAdapter">
+ <property name="targetObject" ref="fooService" />
+ <property name="targetMethod" value="generateFoo" />
+ </bean>
- ]]>
+ <bean id="fooService" class="org.springframework.batch.item.sample.FooService" />
One important point to note is that the contract of the targetMethod
must be the same as the contract for read: when
@@ -1961,13 +1930,13 @@
ItemWriter. The ItemWriter
implementation is equally as simple:
-
-
-
-
+ <bean id="itemWriter" class="org.springframework.batch.item.adapter.ItemWriterAdapter">
+ <property name="targetObject" ref="fooService" />
+ <property name="targetMethod" value="processFoo" />
+ </bean>
-
-]]>
+ <bean id="fooService" class="org.springframework.batch.item.sample.FooService" />
+
@@ -1982,7 +1951,7 @@
that contains another ItemReader. For
example:
- public class CompositeItemWriter implements ItemWriter {
ItemWriter itemWriter;
@@ -2004,7 +1973,7 @@
public void flush() throws FlushFailedException {
itemWriter.flush();
}
-}]]>
+}
The class above contains another ItemWriter
that it delgates to after having provided some business logic. It should
@@ -2021,10 +1990,10 @@
just want to modify the item. For this scenario, Spring Batch provides the
ItemTransformer interface:
- public interface ItemTransformer {
Object transform(Object item) throws Exception;
- }]]>
+ }
An ItemTransformer is very simple, given one
object, transorm it and return another. The object provided may or may not
@@ -2040,7 +2009,7 @@
ItemTransformer can be written that performs the
conversion:
- public class Foo {}
public class Bar {
public Bar(Foo foo) {}
@@ -2063,7 +2032,7 @@
}
//rest of class ommitted for clarity
- }]]>
+ }
In the very simple example above, there is a class
Foo, a class Bar, and a
@@ -2080,10 +2049,10 @@
Bar returned. The resulting
Bar will then be written:
- ItemTransformerItemWriter itemTransformerItemWriter = new ItemTransformerItemWriter();
itemTransformerItemWriter.setItemTransformer(new FooTransformer());
itemTransformerItemWriter.setDelegate(new BarWriter());
- itemTransformerItemWriter.write(new Foo());]]>
+ itemTransformerItemWriter.write(new Foo());
The Delegate Pattern and Registering with the Step
@@ -2116,7 +2085,7 @@
Transformed to Bar, which will be transformed to
Foobar and written out:
- public class Foo {}
public class Bar {
public Bar(Foo foo) {}
@@ -2151,17 +2120,17 @@
}
//rest of class ommitted for clarity
- }]]>
+ }
A FooTransformer and
BarTransformer can be 'chained' together to give
the resultant Foobar:
- CompositeItemTransformer compositeTransformer = new CompositeItemTransformer();
List itemTransformers = new ArrayList();
itemTransformers.add(new FooTransformer());
itemTransformers.add(new BarTransformer());
- compositeTransformer.setItemTransformers(itemTransformers);]]>
+ compositeTransformer.setItemTransformers(itemTransformers);
The compositeTransformer could be said to accept a
Foo and return a Foobar.
@@ -2197,11 +2166,11 @@
rather provides a very simple interface that can be implemented by any
number of frameworks:
- public interface Validator {
void validate(Object value) throws ValidationException;
- }]]>
+ }
The contract is that the validate method
will throw an exception if the object is invalid, and return normally if
@@ -2330,7 +2299,7 @@
basic contract of ItemReader,
read:
- public class CustomItemReader implements ItemReader{
List items;
@@ -2350,14 +2319,14 @@
public void mark() throws MarkFailedException { };
public void reset() throws ResetFailedException { };
- }]]>
+ }
This very simple class takes a list of items, and returns one at a
time, removing it from the list. When the list empty, it returns null,
thus satisfying the most basic requirements of an
ItemReader, as illustrated below:
- List items = new ArrayList();
items.add("1");
items.add("2");
items.add("3");
@@ -2366,7 +2335,7 @@
assertEquals("1", itemReader.read());
assertEquals("2", itemReader.read());
assertEquals("3", itemReader.read());
- assertNull(itemReader.read());]]>
+ assertNull(itemReader.read());
Making the ItemReader
@@ -2382,7 +2351,7 @@
empty, but we'll need to add code to them in order to support the
rollback scenario:
- public class CustomItemReader implements ItemReader{
List items;
int currentIndex = 0;
@@ -2395,7 +2364,7 @@
public Object read() throws Exception, UnexpectedInputException,
NoWorkFoundException, ParseException {
- if (currentIndex < items.size()) {
+ if (currentIndex < items.size()) {
return items.get(currentIndex++);
}
return null;
@@ -2408,7 +2377,7 @@
public void reset() throws ResetFailedException {
currentIndex = lastMarkedIndex;
};
- }]]>
+ }
The CustomItemReader has now been
modified to keep track of where it is currently, and where it was when
@@ -2418,12 +2387,12 @@
ItemReader to the state it was in when
mark was last called:
- //Assume same setup as last example, a list with "1", "2", and "3"
itemReader.mark();
assertEquals("1", itemReader.read());
assertEquals("2", itemReader.read());
itemReader.reset();
- assertEquals("1", itemReader.read());]]>
+ assertEquals("1", itemReader.read());
In most real world scenarios, there will likely be some kind of
underlying resource that will require tracking. In the case of a file,
@@ -2465,7 +2434,7 @@
implemented with the ItemStream
interface:
- public class CustomItemReader implements ItemReader, ItemStream{
List items;
int currentIndex = 0;
@@ -2479,7 +2448,7 @@
public Object read() throws Exception, UnexpectedInputException,
NoWorkFoundException, ParseException {
- if (currentIndex < items.size()) {
+ if (currentIndex < items.size()) {
return items.get(currentIndex++);
}
return null;
@@ -2508,7 +2477,7 @@
};
public void close(ExecutionContext executionContext) throws ItemStreamException {}
- }]]>
+ }
On each call to ItemStream
update method, the current index of the
@@ -2520,7 +2489,7 @@
current index is moved to that location. This is a fairly trivial
example, but it still meets the general contract:
- ExecutionContext executionContext = new ExecutionContext();
((ItemStream)itemReader).open(executionContext);
assertEquals("1", itemReader.read());
((ItemStream)itemReader).update(executionContext);
@@ -2532,7 +2501,7 @@
itemReader = new CustomItemReader(items);
((ItemStream)itemReader).open(executionContext);
- assertEquals("2", itemReader.read());]]>
+ assertEquals("2", itemReader.read());
Most ItemReaders have much more sophisticated restart logic. The
DrivingQueryItemReader, for example, only loads
@@ -2563,7 +2532,7 @@
example. As with the ItemReader example, a List
will be used in order to keep the example as simple as possible:
- public class CustomItemWriter implements ItemWriter{
List output = new ArrayList();
@@ -2574,7 +2543,7 @@
public void clear() throws ClearFailedException { }
public void flush() throws FlushFailedException { }
- }]]>
+ }
Making the ItemReader
@@ -2594,7 +2563,7 @@
flush and clear
should be implemented to allow for a buffering solution:
- public class CustomItemWriter implements ItemWriter{
List output = new ArrayList();
List buffer = new ArrayList();
@@ -2613,7 +2582,7 @@
it.remove();
}
}
- }]]>
+ }
The ItemWriter buffers all output, only
writing to the actual output (in this case by added to a list) when