The ItemReader and
ItemWriter interfaces are both very useful for
their specific tasks, but what if you want to insert business logic before
writing? One option for both reading and writing is to use the composite
pattern: create an ItemWriter that contains another
ItemWriter, or an ItemReader
that contains another ItemReader. For
example:
public class CompositeItemWriter<T> implements ItemWriter<T> {
ItemWriter<T> itemWriter;
public CompositeItemWriter(ItemWriter<T> itemWriter) {
this.itemWriter = itemWriter;
}
public void write(List<? extends T> items) throws Exception {
//Add business logic here
itemWriter.write(item);
}
public void setDelegate(ItemWriter<T> itemWriter){
this.itemWriter = itemWriter;
}
}The class above contains another ItemWriter
to which it delegates after having provided some business logic. This
pattern could easily be used for an ItemReader as
well, perhaps to obtain more reference data based upon the input that was
provided by the main ItemReader. It is also useful
if you need to control the call to write yourself.
However, if you only want to 'transform' the item passed in for writing
before it is actually written, there isn't much need to call
write yourself: you just want to modify the item.
For this scenario, Spring Batch provides the
ItemProcessor interface:
public interface ItemProcessor<I, O> {
O process(I item) throws Exception;
}An ItemProcessor is very simple; given one
object, transform it and return another. The provided object may or may
not be of the same type. The point is that business logic may be applied
within process, and is completely up to the developer to create. An
ItemProcessor can be wired directly into a step,
For example, assuming an ItemReader provides a
class of type Foo, and it needs to be converted to type Bar before being
written out. An ItemProcessor can be written that
performs the conversion:
public class Foo {}
public class Bar {
public Bar(Foo foo) {}
}
public class FooProcessor implements ItemProcessor<Foo,Bar>{
public Bar process(Foo foo) throws Exception {
//Perform simple transformation, convert a Foo to a Bar
return new Bar(foo);
}
}
public class BarWriter implements ItemWriter<Bar>{
public void write(List<? extends Bar> bars) throws Exception {
//write bars
}
}In the very simple example above, there is a class
Foo, a class Bar, and a
class FooProcessor that adheres to the
ItemProcessor interface. The transformation is
simple, but any type of transformation could be done here. The
BarWriter will be used to write out
Bar objects, throwing an exception if any other
type is provided. Similarly, the FooProcessor will
throw an exception if anything but a Foo is
provided. The FooProcessor can then be injected
into a Step:
<job id="ioSampleJob">
<step name="step1">
<tasklet>
<chunk reader="fooReader" processor="fooProcessor" writer="barWriter"
commit-interval="2"/>
</tasklet>
</step>
</job>Performing a single transformation is useful in many scenarios,
but what if you want to 'chain' together multiple
ItemProcessors? This can be accomplished using
the composite pattern mentioned previously. To update the previous,
single transformation, example, Foo will be
transformed to Bar, which will be transformed to
Foobar and written out:
public class Foo {}
public class Bar {
public Bar(Foo foo) {}
}
public class Foobar{
public Foobar(Bar bar) {}
}
public class FooProcessor implements ItemProcessor<Foo,Bar>{
public Bar process(Foo foo) throws Exception {
//Perform simple transformation, convert a Foo to a Bar
return new Bar(foo);
}
}
public class BarProcessor implements ItemProcessor<Bar,FooBar>{
public FooBar process(Bar bar) throws Exception {
return new Foobar(bar);
}
}
public class FoobarWriter implements ItemWriter<FooBar>{
public void write(List<? extends FooBar> items) throws Exception {
//write items
}
}A FooProcessor and
BarProcessor can be 'chained' together to give
the resultant Foobar:
CompositeItemProcessor<Foo,Foobar> compositeProcessor =
new CompositeItemProcessor<Foo,Foobar>();
List itemProcessors = new ArrayList();
itemProcessors.add(new FooTransformer());
itemProcessors.add(new BarTransformer());
compositeProcessor.setDelegates(itemProcessors);Just as with the previous example, the composite processor can be
configured into the Step:
<job id="ioSampleJob">
<step name="step1">
<tasklet>
<chunk reader="fooReader" processor="compositeProcessor" writer="foobarWriter"
commit-interval="2"/>
</tasklet>
</step>
</job>
<bean id="compositeItemProcessor"
class="org.springframework.batch.item.support.CompositeItemProcessor">
<property name="delegates">
<list>
<bean class="..FooProcessor" />
<bean class="..BarProcessor" />
</list>
</property>
</bean>One typical use for an item processor is to filter out records before they are passed to the ItemWriter. Filtering is an action distinct from skipping; skipping indicates that a record is invalid whereas filtering simply indicates that a record should not be written.
For example, consider a batch job that reads a file containing
three different types of records: records to insert, records to update,
and records to delete. If record deletion is not supported by the
system, then we would not want to send any "delete" records to the
ItemWriter. But, since these records are not
actually bad records, we would want to filter them out, rather than
skip. As a result, the ItemWriter would receive only "insert" and
"update" records.
To filter a record, one simply returns "null" from the
ItemProcessor. The framework will detect that the
result is "null" and avoid adding that item to the list of records
delivered to the ItemWriter. As usual, an
exception thrown from the ItemProcessor will
result in a skip.
When a chunk is rolled back, items that have been cached during reading may be reprocessed. If a step is configured to be fault tolerant (uses skip or retry processing typically), any ItemProcessor used should be implemented in a way that is idempotent. Typically that would consist of performing no changes on the input item for the ItemProcessor and only updating the instance that is the result.