diff --git a/spring-batch-docs/asciidoc/readersAndWriters.adoc b/spring-batch-docs/asciidoc/readersAndWriters.adoc
index 61f196886..c4ddc2eb7 100644
--- a/spring-batch-docs/asciidoc/readersAndWriters.adoc
+++ b/spring-batch-docs/asciidoc/readersAndWriters.adoc
@@ -6,6 +6,8 @@
== ItemReaders and ItemWriters
+include::toggle.adoc[]
+
All batch processing can be described in its most simple form as
reading in large amounts of data, performing some type of calculation or
transformation, and writing the result out. Spring Batch provides three key
@@ -210,8 +212,8 @@ throw an exception if anything but a `Foo` is
provided. The `FooProcessor` can then be injected
into a `Step`:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -223,6 +225,28 @@ into a `Step`:
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public Job ioSampleJob() {
+ return this.jobBuilderFactory.get("ioSampleJOb")
+ .start(step1())
+ .end()
+ .build();
+}
+
+@Bean
+public Step step1() {
+ return this.stepBuilderFactory.get("step1")
+ .chunk(2)
+ .reader(fooReader())
+ .processor(fooProcessor())
+ .writer(barWriter())
+ .build();
+}
+----
+
[[chainingItemProcessors]]
@@ -287,8 +311,8 @@ compositeProcessor.setDelegates(itemProcessors);
Just as with the previous example, the composite processor can be
configured into the `Step`:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -310,6 +334,41 @@ Just as with the previous example, the composite processor can be
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public Job ioSampleJob() {
+ return this.jobBuilderFactory.get("ioSampleJob")
+ .start(step1())
+ .end()
+ .build();
+}
+
+@Bean
+public Step step1() {
+ return this.stepBuilderFactory.get("step1")
+ .chunk(2)
+ .reader(fooReader())
+ .processor(compositeProcessor())
+ .writer(foobarWriter())
+ .build();
+}
+
+@Bean
+public CompositeItemProcessor compositeProcessor() {
+ List delegates = new ArrayList<>(2);
+ delegates.add(new FooProcessor());
+ delegates.add(new BarProcessor());
+
+ CompositeItemProcessor processor = new CompositeItemProcessor();
+
+ processor.setDelegates(delegates);
+
+ return processor;
+}
+----
+
[[filiteringRecords]]
@@ -419,14 +478,15 @@ Note that the `CompositeItemWriter` is an
are not known to the `Step`, they need to be injected
as listeners or streams (or both if appropriate):
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
-
+
@@ -441,6 +501,44 @@ Note that the `CompositeItemWriter` is an
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public Job ioSampleJob() {
+ return this.jobBuilderFactory.get("ioSampleJob")
+ .start(step1())
+ .end()
+ .build();
+}
+
+@Bean
+public Step step1() {
+ return this.stepBuilderFactory.get("step1")
+ .chunk(2)
+ .reader(fooReader())
+ .processor(fooProcessor())
+ .writer(compositeItemWriter())
+ .stream(barWriter())
+ .build();
+}
+
+@Bean
+public CustomCompositeItemWriter compositeItemWriter() {
+
+ CustomCompositeItemWriter writer = new CustomCompositeItemWriter();
+
+ writer.setDelegate(barWriter());
+
+ return writer;
+}
+
+@Bean
+public BarWriter barWriter() {
+ return new BarWriter();
+}
+----
+
[[flatFiles]]
=== Flat Files
@@ -878,8 +976,8 @@ the JavaBean specification. Again using the football example, the
`BeanWrapperFieldSetMapper` configuration looks
like the following:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -891,6 +989,25 @@ like the following:
scope="prototype" />
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public FieldSetMapper fieldSetMapper() {
+ BeanWrapperFieldSetMapper fieldSetMapper = new BeanWrapperFieldSetMapper();
+
+ fieldSetMapper.setPrototypeBeanName("player");
+
+ return fieldSetMapper;
+}
+
+@Bean
+@Scope("prototype")
+public Player player() {
+ return new Player();
+}
+----
+
For each entry in the `FieldSet`, the
mapper will look for a corresponding setter on a new instance of the
`Player` object (for this reason, prototype scope
@@ -936,8 +1053,8 @@ While this looks like one large field, it actually represent 4 distinct fields:
When configuring the `FixedLengthLineTokenizer`, each of these lengths
must be provided in the form of ranges:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -946,14 +1063,7 @@ must be provided in the form of ranges:
----
-Because the `FixedLengthLineTokenizer` uses
-the same `LineTokenizer` interface as discussed
-above, it will return the same `FieldSet` as if a
-delimiter had been used. This allows the same approaches to be used in
-handling its output, such as using the
-`BeanWrapperFieldSetMapper`.
-
-
+[role="xmlContent"]
[NOTE]
====
Supporting the above syntax for ranges requires that a
@@ -965,6 +1075,30 @@ is automatically declared in an
namespace is used.
====
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public FixedLengthTokenizer fixedLengthTokenizer() {
+ FixedLengthTokenizer tokenizer = new FixedLengthTokenizer();
+
+ tokenizer.setNames("ISIN", "Quantity", "Price", "Customer");
+ tokenizer.setColumns(new Range(1-12),
+ new Range(13-15),
+ new Range(16-20),
+ new Range(21-29));
+
+ return tokenizer;
+}
+----
+
+Because the `FixedLengthLineTokenizer` uses
+the same `LineTokenizer` interface as discussed
+above, it will return the same `FieldSet` as if a
+delimiter had been used. This allows the same approaches to be used in
+handling its output, such as using the
+`BeanWrapperFieldSetMapper`.
+
[[prefixMatchingLineMapper]]
===== Multiple Record Types within a Single File
@@ -997,8 +1131,8 @@ easy by allowing maps of patterns to
`LineTokenizers` and patterns to
`FieldSetMappers` to be configured:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -1018,6 +1152,31 @@ easy by allowing maps of patterns to
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public PatternMatchingCompositeLineMapper orderFileLineMapper() {
+ PatternMatchingCompositeLineMapper lineMapper =
+ new PatternMatchingCompositeLineMapper();
+
+ Map tokenizers = new HashMap<>(3);
+ tokenizers.put("USER*", userTokenizer());
+ tokenizers.put("LINEA*", lineATokenizer());
+ tokenizers.put("LINEB*", lineBTokenizer());
+
+ lineMapper.setTokenizers(tokenizers);
+
+ Map mappers = new HashMap<>(2);
+ mappers.put("USER*", userFieldSetMapper());
+ mappers.put("LINE*", lineFieldSetMapper());
+
+ lineMapper.setFieldSetMappers(mappers);
+
+ return lineMapper;
+}
+----
+
In this example, "LINEA" and "LINEB" have separate
`LineTokenizers` but they both use the same
`FieldSetMapper`.
@@ -1038,12 +1197,20 @@ listed as patterns, "LINEA" would match pattern "LINEA*", while
("*") can serve as a default by matching any line not matched by any
other pattern.
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+...
+tokenizers.put("*", defaultLineTokenizer());
+...
+----
+
There is also a
`PatternMatchingCompositeLineTokenizer` that can
be used for tokenization alone.
@@ -1244,8 +1411,8 @@ public void write(T item) throws Exception {
A simple configuration would look like the following:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -1255,6 +1422,19 @@ A simple configuration would look like the following:
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public FlatFileItemWriter itemWriter() {
+ return new FlatFileItemWriterBuilder()
+ .name("itemWriter")
+ .resource(new FileSystemResource("file:target/test-outputs/output.txt"))
+ .lineAggregator(new PassThroughLineAggregator<>())
+ .build();
+}
+----
+
[[FieldExtractor]]
===== FieldExtractor
@@ -1385,8 +1565,8 @@ Because a domain object is being used, an implementation of the
`FieldExtractor` interface must be provided, along with the delimiter to
use:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -1403,6 +1583,27 @@ use:
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public FlatFileItemWriter itemWriter(Resource outputResource) throws Exception {
+ BeanWrapperFieldExtractor fieldExtractor = new BeanWrapperFieldExtractor<>();
+ fieldExtractor.setNames(new String[] {"name", "credit"});
+ fieldExtractor.afterPropertiesSet();
+
+ DelimitedLineAggregator lineAggregator = new DelimitedLineAggregator<>();
+ lineAggregator.setDelimiter(",");
+ lineAggregator.setFieldExtractor(fieldExtractor);
+
+ return new FlatFileItemWriterBuilder()
+ .name("foo")
+ .resource(outputResource)
+ .lineAggregator(lineAggregator)
+ .build();
+}
+----
+
In this case, the
`BeanWrapperFieldExtractor` described earlier in
this chapter is used to turn the name and credit fields within
@@ -1421,8 +1622,8 @@ file writing via the `FormatterLineAggregator`.
Using the same `CustomerCredit` domain object
described above, it can be configured as follows:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -1439,15 +1640,44 @@ described above, it can be configured as follows:
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public FlatFileItemWriter itemWriter(Resource outputResource) throws Exception {
+ BeanWrapperFieldExtractor fieldExtractor = new BeanWrapperFieldExtractor<>();
+ fieldExtractor.setNames(new String[] {"name", "credit"});
+ fieldExtractor.afterPropertiesSet();
+
+ FormatterLineAggregator lineAggregator = new FormatterLineAggregator<>();
+ lineAggregator.setFormat("%-9s%-2.0f");
+ lineAggregator.setFieldExtractor(fieldExtractor);
+
+ return new FlatFileItemWriterBuilder()
+ .name("foo")
+ .resource(outputResource)
+ .lineAggregator(lineAggregator)
+ .build();
+}
+----
+
Most of the above example should look familiar. However, the
value of the format property is new:
-[source, xml]
+[source, xml, role="xmlContent"]
----
----
+[source, java, role="javaContent"]
+----
+...
+FormatterLineAggregator lineAggregator = new FormatterLineAggregator<>();
+lineAggregator.setFormat("%-9s%-2.0f");
+...
+----
+
The underlying implementation is built using the same
`Formatter` added as part of Java 5. The Java
`Formatter` is based on the
@@ -1576,8 +1806,8 @@ object.
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -1586,6 +1816,21 @@ object.
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public StaxEventItemReader itemReader() {
+ return new StaxEventItemReaderBuilder()
+ .name("itemReader")
+ .resource(new FileSystemResource("data/iosample/input/input.xml"))
+ .addFragmentRootElements("trade")
+ .unmarshaller(tradeMarshaller())
+ .build();
+
+}
+----
+
Notice that in this example we have chosen to use an
`XStreamMarshaller` which accepts an alias passed
in as a map with the first key and value being the name of the fragment
@@ -1595,8 +1840,8 @@ map to fields within the object type are described as key/value pairs in
the map. In the configuration file we can use a Spring configuration
utility to describe the required alias as follows:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -1611,6 +1856,24 @@ utility to describe the required alias as follows:
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public XStreamMarshaller tradeMarshaller() {
+ Map aliases = new HashMap<>();
+ aliases.put("trade", Trade.class);
+ aliases.put("price", BigDecimal.class);
+ aliases.put("name", String.class);
+
+ XStreamMarshaller marshaller = new XStreamMarshaller();
+
+ marshaller.setAliases(aliases);
+
+ return marshaller;
+}
+----
+
On input the reader reads the XML resource until it recognizes
that a new fragment is about to start (by matching the tag name by
default). The reader creates a standalone XML document from the fragment
@@ -1670,8 +1933,8 @@ the OXM tools. We'll show this in an example using the
MarshallingEventWriterSerializer. The Spring
configuration for this setup looks as follows:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -1681,14 +1944,30 @@ configuration for this setup looks as follows:
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public StaxEventItemWriter itemWriter(Resource outputResource) {
+ return new StaxEventItemWriterBuilder()
+ .name("fooWriter")
+ .marshaller(customerCreditMarshaller())
+ .resource(outputResource)
+ .rootTagName("customers")
+ .overwriteOutput(true)
+ .build();
+
+}
+----
+
The configuration sets up the three required properties and
optionally sets the overwriteOutput=true, mentioned earlier in the
chapter for specifying whether an existing file can be overwritten. It
should be noted the marshaller used for the writer is the exact same as
the one used in the reading example from earlier in the chapter:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -1703,6 +1982,24 @@ the one used in the reading example from earlier in the chapter:
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public XStreamMarshaller customerCreditMarshaller() {
+ XStreamMarshaller marshaller = new XStreamMarshaller();
+
+ Map aliases = new HashMap<>();
+ aliases.put("customer", CustomerCredit.class);
+ aliases.put("credit", BigDecimal.class);
+ aliases.put("name", String.class);
+
+ marshaller.setAliases(aliases);
+
+ return marshaller;
+}
+----
+
To summarize with a Java example, the following code illustrates
all of the points discussed, demonstrating the programmatic setup of the
required properties:
@@ -1710,7 +2007,6 @@ required properties:
[source, java]
----
-StaxEventItemWriter staxItemWriter = new StaxEventItemWriter()
FileSystemResource resource = new FileSystemResource("data/outputFile.xml")
Map aliases = new HashMap();
@@ -1720,10 +2016,16 @@ aliases.put("name","java.lang.String");
Marshaller marshaller = new XStreamMarshaller();
marshaller.setAliases(aliases);
-staxItemWriter.setResource(resource);
-staxItemWriter.setMarshaller(marshaller);
-staxItemWriter.setRootTagName("trades");
-staxItemWriter.setOverwriteOutput(true);
+StaxEventItemWriter staxItemWriter =
+ new StaxEventItemWriterBuilder()
+ .name("creditWriter")
+ .marshaller(marshaller)
+ .resource(resource)
+ .rootTagName("trades")
+ .overwriteOutput(true)
+ .build();
+
+staxItemWriter.afterPropertiesSet();
ExecutionContext executionContext = new ExecutionContext();
staxItemWriter.open(executionContext);
@@ -1754,8 +2056,8 @@ file-1.txt and file-2.txt are formatted the same and for business
MuliResourceItemReader can be used to read in both
files by using wildcards:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -1763,6 +2065,18 @@ file-1.txt and file-2.txt are formatted the same and for business
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public MultiResourceItemReader multiResourceReader() {
+ return new MultiResourceItemReaderBuilder()
+ .delegate(flatFileItemReader())
+ .resources(resources())
+ .build();
+}
+----
+
The referenced delegate is a simple
`FlatFileItemReader`. The above configuration will
read input from both files, handling rollback and restart scenarios. It
@@ -1932,8 +2246,8 @@ of high performance batch processing. Furthermore, it is very easily
configured for injection into a Spring Batch
`Step`:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -1944,6 +2258,21 @@ configured for injection into a Spring Batch
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public JdbcCursorItemReader itemReader() {
+ return new JdbcCursorItemReaderBuilder()
+ .dataSource(this.dataSource)
+ .name("creditReader")
+ .sql("select ID, NAME, CREDIT from CUSTOMER")
+ .rowMapper(new CustomerCreditRowMapper())
+ .build();
+
+}
+----
+
[[JdbcCursorItemReaderProperties]]
====== Additional Properties
@@ -2070,8 +2399,8 @@ underlying cursor can be set via the setFetchSize property. As with
`JdbcCursorItemReader`, configuration is
straightforward:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -2080,6 +2409,19 @@ straightforward:
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public HibernateCursorItemReader itemReader(SessionFactory sessionFactory) {
+ return new HibernateCursorItemReaderBuilder()
+ .name("creditReader")
+ .sessionFactory(sessionFactory)
+ .queryString("from CustomerCredit")
+ .build();
+}
+----
+
[[StoredProcedureItemReader]]
===== StoredProcedureItemReader
@@ -2101,8 +2443,8 @@ cursor in three different ways:
Below is a basic example configuration using the same 'customer
credit' example as earlier:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -2111,9 +2453,24 @@ credit' example as earlier:
-
----
+.Java Configuration
+[source, xml, role="javaContent"]
+----
+@Bean
+public StoredProcedureItemReader reader(DataSource dataSource) {
+ StoredProcedureItemReader reader = new StoredProcedureItemReader();
+
+ reader.setDataSource(dataSource);
+ reader.setProcedureName("sp_customer_credit");
+ reader.setRowMapper(new CustomerCreditRowMapper());
+
+ return reader;
+}
+----
+//TODO: Fix the above config to use a builder once we have one for it.
+
This example relies on the stored procedure to provide a
`ResultSet` as a returned result (option 1 above).
@@ -2122,7 +2479,8 @@ would need to provide the position of the out parameter that is the
returned ref-cursor. Here is an example where the first parameter is
the returned ref-cursor:
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -2132,7 +2490,22 @@ the returned ref-cursor:
+----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public StoredProcedureItemReader reader(DataSource dataSource) {
+ StoredProcedureItemReader reader = new StoredProcedureItemReader();
+
+ reader.setDataSource(dataSource);
+ reader.setProcedureName("sp_customer_credit");
+ reader.setRowMapper(new CustomerCreditRowMapper());
+ reader.setRefCursorPosition(1);
+
+ return reader;
+}
----
If the cursor was returned from a stored function (option 3) we
@@ -2140,8 +2513,8 @@ would need to set the property "[maroon]#function#" to
`true`. It defaults to `false`. Here
is what that would look like:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -2151,7 +2524,22 @@ is what that would look like:
+----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public StoredProcedureItemReader reader(DataSource dataSource) {
+ StoredProcedureItemReader reader = new StoredProcedureItemReader();
+
+ reader.setDataSource(dataSource);
+ reader.setProcedureName("sp_customer_credit");
+ reader.setRowMapper(new CustomerCreditRowMapper());
+ reader.setFunction(true);
+
+ return reader;
+}
----
In all of these cases we need to define a
@@ -2165,8 +2553,8 @@ If the stored procedure or function takes in parameter then they
the out parameter that returns the ref-cursor, the second and third
are in parameters that takes a value of type INTEGER:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -2199,6 +2587,29 @@ If the stored procedure or function takes in parameter then they
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public StoredProcedureItemReader reader(DataSource dataSource) {
+ List parameters = new ArrayList<>();
+ parameters.add(new SqlOutParameter("newId", OracleTypes.CURSOR));
+ parameters.add(new SqlParameter("amount", Types.INTEGER);
+ parameters.add(new SqlParameter("custId", Types.INTEGER);
+
+ StoredProcedureItemReader reader = new StoredProcedureItemReader();
+
+ reader.setDataSource(dataSource);
+ reader.setProcedureName("spring.cursor_func");
+ reader.setParameters(parameters);
+ reader.setRefCursorPosition(1);
+ reader.setRowMapper(rowMapper());
+ reader.setPreparedStatementSetter(parameterSetter());
+
+ return reader;
+}
+----
+
In addition to the parameter declarations we need to specify a
`PreparedStatementSetter` implementation that
sets the parameter values for the call. This works the same as for the
@@ -2247,8 +2658,8 @@ After the reader has been opened, it will pass back one item per
Below is an example configuration using a similar 'customer
credit' example as the cursor based `ItemReaders` above:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -2270,6 +2681,37 @@ Below is an example configuration using a similar 'customer
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public JdbcPagingItemReader itemReader(DataSource dataSource) {
+ Map parameterValues = new HashMap<>();
+ parameterValues.put("status", "NEW");
+
+ return new JdbcPagingItemReaderBuilder()
+ .name("creditReader")
+ .dataSource(dataSource)
+ .queryProvider(queryProvider())
+ .parameterValues(parameterValues)
+ .rowMapper(customerMapper())
+ .pageSize(1000)
+ .build();
+}
+
+@Bean
+public SqlPagingQueryProviderFactoryBean queryProvider() {
+ SqlPagingQueryProviderFactoryBean provider = new SqlPagingQueryProviderFactoryBean();
+
+ provider.setSelectClause("select id, name, credit");
+ provider.setFromClause("from customer");
+ provider.setWhereClause("where status=:status");
+ provider.setSortKey("id");
+
+ return provider;
+}
+----
+
This configured `ItemReader` will return
`CustomerCredit` objects using the
`RowMapper` that must be specified. The
@@ -2307,8 +2749,8 @@ happens behind the scenes when additional entities are needed. Below
is an example configuration using the same 'customer credit' example
as the JDBC reader above:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -2317,6 +2759,20 @@ as the JDBC reader above:
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public JpaPagingItemReader itemReader() {
+ return new JpaPagingItemReaderBuilder()
+ .name("credit")
+ .entityManagerFactory(entityManagerFactory())
+ .queryString("select c from CustomerCredit c")
+ .pageSize(1000)
+ .build();
+}
+----
+
This configured `ItemReader` will return
`CustomerCredit` objects in the exact same manner
as described by the `JdbcPagingItemReader` above,
@@ -2404,8 +2860,8 @@ it is such a common concern, Spring Batch provides implementations:
standard Spring method invoking the delegate pattern and are fairly simple
to set up. Below is an example of the reader:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -2415,6 +2871,25 @@ to set up. Below is an example of the reader:
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public ItemReaderAdapter itemReader() {
+ ItemReaderAdapter reader = new ItemReaderAdapter();
+
+ reader.setTargetObject(fooService());
+ reader.setTargetMethod("generateFoo");
+
+ return reader;
+}
+
+@Bean
+public FooService fooService() {
+ return new FooService();
+}
+----
+
One important point to note is that the contract of the targetMethod
must be the same as the contract for `read`: when
exhausted it will return null, otherwise an `Object`.
@@ -2424,8 +2899,8 @@ depending upon the implementation of the
`ItemWriter`. The `ItemWriter`
implementation is equally as simple:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -2433,7 +2908,25 @@ implementation is equally as simple:
+----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public ItemWriterAdapter itemWriter() {
+ ItemWriterAdapter writer = new ItemWriterAdapter();
+
+ writer.setTargetObject(fooService());
+ writer.setTargetMethod("processFoo");
+
+ return writer;
+}
+
+@Bean
+public FooService fooService() {
+ return new FooService();
+}
----
[[validatingInput]]
@@ -2473,42 +2966,41 @@ will throw an exception if the object is invalid, and return normally if
it is valid. Spring Batch provides an out of the box
`ItemProcessor`:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
-
-
-
-
-
- 0 AND ? <= 9999999999 : 'Incorrect order ID' : 'error.order.id' }
- { totalLines : ? = size(lineItems) : 'Bad count of order lines'
- : 'error.order.lines.badcount'}
- { customer.registered : customer.businessCustomer = FALSE OR ? = TRUE
- : 'Business customer must be registered'
- : 'error.customer.registration'}
- { customer.companyName : customer.businessCustomer = FALSE OR ? HAS TEXT
- : 'Company name for business customer is mandatory'
- :'error.customer.companyname'}
- ]]>
-
-
-
-
+
+
+
+
----
-This simple example shows a simple
-`ValangValidator` that is used to validate an order
-object. The intent is not to show Valang functionality as much as to show
-how a validator could be added.
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public ValidatingItemProcessor itemProcessor() {
+ ValidatingItemProcessor processor = new ValidatingItemProcessor();
+
+ processor.setValidator(validator());
+
+ return processor;
+}
+
+@Bean
+public SpringValidator validator() {
+ SpringValidator validator = new SpringValidator();
+
+ validator.setValidator(new TradeValidator());
+
+ return validator;
+}
+----
[[process-indicator]]
@@ -2531,8 +3023,8 @@ state, such as the current row number, since it will be irrelevant upon
restart. For this reason, all readers and writers include the 'saveState'
property:
-
-[source, xml]
+.XML Configuration
+[source, xml, role="xmlContent"]
----
@@ -2553,6 +3045,26 @@ property:
----
+.Java Configuration
+[source, java, role="javaContent"]
+----
+@Bean
+public JdbcCursorItemReader playerSummarizationSource(DataSource dataSource) {
+ return new JdbcCursorItemReaderBuilder()
+ .dataSource(dataSource)
+ .rowMapper(new PlayerSummaryMapper())
+ .saveState(false)
+ .sql("SELECT games.player_id, games.year_no, SUM(COMPLETES),"
+ + "SUM(ATTEMPTS), SUM(PASSING_YARDS), SUM(PASSING_TD),"
+ + "SUM(INTERCEPTIONS), SUM(RUSHES), SUM(RUSH_YARDS),"
+ + "SUM(RECEPTIONS), SUM(RECEPTIONS_YARDS), SUM(TOTAL_TD)"
+ + "from games, players where players.player_id ="
+ + "games.player_id group by games.player_id, games.year_no")
+ .build();
+
+}
+----
+
The `ItemReader` configured above will not make
any entries in the `ExecutionContext` for any
executions in which it participates.