diff --git a/src/site/docbook/reference/common-patterns.xml b/src/site/docbook/reference/common-patterns.xml index e17d00fc3..8e64fb7ff 100644 --- a/src/site/docbook/reference/common-patterns.xml +++ b/src/site/docbook/reference/common-patterns.xml @@ -1,7 +1,7 @@ - - + + Common Batch Patterns Some batch jobs can be assembled purely from off-the-shelf components @@ -36,9 +36,9 @@ write. The below code snippets illustrate a listener that logs both read and write failures: - public class ItemFailureLoggerListener extends ItemListenerSupport { + public class ItemFailureLoggerListener extends ItemListenerSupport { - private static Log logger = LogFactory.getLog("item.error"); + private static Log logger = LogFactory.getLog("item.error"); public void onReadError(Exception ex) { logger.error("Encountered error on read", e); @@ -53,7 +53,7 @@ Having implemented this listener it must be registered with the step: - <step id="simpleStep"> + <step id="simpleStep"> ... <listeners> <listener> @@ -85,8 +85,8 @@ indefinitely or skipped). For example, a custom exception type could be used, as in the example below: - public class PoisonPillItemWriter implements ItemWriter<T> { - + public class PoisonPillItemWriter implements ItemWriter<T> { + public void write(T item) throws Exception { if (isPoisonPill(item)) { throw new PoisonPillException("Posion pill detected: " + item); @@ -98,12 +98,12 @@ Another simple way to stop a step from executing is to simply return null from the ItemReader: - public class EarlyCompletionItemReader implements ItemReader<T> { + public class EarlyCompletionItemReader implements ItemReader<T> { private ItemReader<T> delegate; public void setDelegate(ItemReader<T> delegate) { ... } - + public T read() throws Exception { T item = delegate.read(); if (isEndItem(item)) { @@ -121,9 +121,9 @@ injected into the Step through the SimpleStepFactoryBean: - <step id="simpleStep"> + <step id="simpleStep"> <tasklet> - <chunk reader="reader" writer="writer" commit-interval="10" + <chunk reader="reader" writer="writer" commit-interval="10" chunk-completion-policy="completionPolicy"/> </tasklet> </step> @@ -139,9 +139,9 @@ the Step. Here is an example of a listener that sets the flag: - public class CustomItemWriter extends ItemListenerSupport implements StepListener { + public class CustomItemWriter extends ItemListenerSupport implements StepListener { - private StepExecution stepExecution; + private StepExecution stepExecution; public void beforeStep(StepExecution stepExecution) { this.stepExecution = stepExecution; @@ -173,7 +173,7 @@ FlatFileHeaderCallback) are optional properties of the FlatFileItemWriter: - <bean id="itemWriter" class="org.spr...FlatFileItemWriter"> + <bean id="itemWriter" class="org.spr...FlatFileItemWriter"> <property name="resource" ref="outputResource" /> <property name="lineAggregator" ref="lineAggregator"/> <property name="headerCallback" ref="headerCallback" /> @@ -183,7 +183,7 @@ The footer callback interface is very simple. It has just one method that is called when the footer must be written: - public interface FlatFileFooterCallback { + public interface FlatFileFooterCallback { void writeFooter(Writer writer) throws IOException; @@ -203,7 +203,7 @@ Trades is placed in a footer, then the following ItemWriter implementation can be used: - public class TradeItemWriter implements ItemWriter<Trade>, + public class TradeItemWriter implements ItemWriter<Trade>, FlatFileFooterCallback { private ItemWriter<Trade> delegate; @@ -250,7 +250,7 @@ FlatFileItemWriter as the footerCallback: - <bean id="tradeItemWriter" class="..TradeItemWriter"> + <bean id="tradeItemWriter" class="..TradeItemWriter"> <property name="delegate" ref="flatFileItemWriter" /> </bean> @@ -270,7 +270,7 @@ with the methods open and update: - public void open(ExecutionContext executionContext) { + public void open(ExecutionContext executionContext) { if (executionContext.containsKey("total.amount") { totalAmount = (BigDecimal) executionContext.get("total.amount"); } @@ -378,7 +378,7 @@ FOT;2;2;267.34 ItemReader should be implemented as a wrapper for the FlatFileItemReader. - <bean id="itemReader" class="org.spr...MultiLineTradeItemReader"> + <bean id="itemReader" class="org.spr...MultiLineTradeItemReader"> <property name="delegate"> <bean class="org.springframework.batch.item.file.FlatFileItemReader"> <property name="resource" value="data/iosample/input/multiLine.txt" /> @@ -403,7 +403,7 @@ FOT;2;2;267.34 deliver a FieldSet for each line back to the wrapping ItemReader. - <bean id="orderFileTokenizer" class="org.spr...PatternMatchingCompositeLineTokenizer"> + <bean id="orderFileTokenizer" class="org.spr...PatternMatchingCompositeLineTokenizer"> <property name="tokenizers"> <map> <entry key="HEA*" value-ref="headerRecordTokenizer" /> @@ -422,7 +422,7 @@ FOT;2;2;267.34 ItemProcessor and ItemWriter. - private FlatFileItemReader<FieldSet> delegate; + private FlatFileItemReader<FieldSet> delegate; public Trade read() throws Exception { Trade t = null; @@ -466,7 +466,7 @@ public Trade read() throws Exception { Tasklet implementation for calling system commands: - <bean class="org.springframework.batch.core.step.tasklet.SystemCommandTasklet"> + <bean class="org.springframework.batch.core.step.tasklet.SystemCommandTasklet"> <property name="command" value="echo hello" /> <!-- 5 second timeout for the command to complete --> <property name="timeout" value="5000" /> @@ -490,7 +490,7 @@ public Trade read() throws Exception { a common use case, a listener is provided with just this functionality: - public class NoWorkFoundStepExecutionListener extends StepExecutionListenerSupport { + public class NoWorkFoundStepExecutionListener extends StepExecutionListenerSupport { public ExitStatus afterStep(StepExecution stepExecution) { if (stepExecution.getReadCount() == 0) { @@ -536,7 +536,7 @@ public Trade read() throws Exception { during Step execution and if the Step fails, that data will be lost. - public class SavingItemWriter implements ItemWriter<Object> { + public class SavingItemWriter implements ItemWriter<Object> { private StepExecution stepExecution; public void write(List<? extends Object> items) throws Exception { @@ -564,7 +564,7 @@ public Trade read() throws Exception { listeners, it must be registered on the Step. - <job id="job1"> + <job id="job1"> <step id="step1"> <tasklet> <chunk reader="reader" writer="savingWriter" commit-interval="10"/> @@ -586,7 +586,7 @@ public Trade read() throws Exception { Finally, the saved values must be retrieved from the Job ExeuctionContext: - public class RetrievingItemWriter implements ItemWriter<Object> { + public class RetrievingItemWriter implements ItemWriter<Object> { private Object someObject; public void write(List<? extends Object> items) throws Exception { diff --git a/src/site/docbook/reference/domain.xml b/src/site/docbook/reference/domain.xml index 4870d9298..030c0d8aa 100644 --- a/src/site/docbook/reference/domain.xml +++ b/src/site/docbook/reference/domain.xml @@ -1,7 +1,6 @@ - - + The Domain Language of Batch To any experienced batch architect, the overall concepts of batch @@ -114,7 +113,7 @@ namespace abstracts away the need to instantiate it directly. Instead, the <job> tag can be used: - <job id="footballJob"> + <job id="footballJob"> <step id="playerload" next="gameLoad"/> <step id="gameLoad" next="playerSummarization"/> <step id="playerSummarization"/> @@ -748,7 +747,7 @@ is to put the current number of lines read into the context, and the framework will do the rest: - executionContext.putLong(getKey(LINES_READ_COUNT), reader.getPosition()); + executionContext.putLong(getKey(LINES_READ_COUNT), reader.getPosition()); Using the EndOfDay example from the Job Stereotypes section as an example, assume there's one step: 'loadData', that loads a file into the @@ -895,7 +894,7 @@ ItemReader is opened, it can check to see if it has any stored state in the context, and initialize itself from there: - if (executionContext.containsKey(getKey(LINES_READ_COUNT))) { + if (executionContext.containsKey(getKey(LINES_READ_COUNT))) { log.debug("Initializing for restart. Restart data is: " + executionContext); long lineCount = executionContext.getLong(getKey(LINES_READ_COUNT)); @@ -946,7 +945,7 @@ StepExecution. For example, consider the following code snippet: - ExecutionContext ecStep = stepExecution.getExecutionContext(); + ExecutionContext ecStep = stepExecution.getExecutionContext(); ExecutionContext ecJob = jobExecution.getExecutionContext(); //ecStep does not equal ecJob @@ -971,7 +970,7 @@ ExecutionContext ecJob = jobExecution.getExecutionContext(); JobExecution implementations are persisted by passing them to the repository: - <job-repository id="jobRepository"/> + <job-repository id="jobRepository"/>
@@ -981,7 +980,7 @@ ExecutionContext ecJob = jobExecution.getExecutionContext(); launching a Job with a given set of JobParameters: - public interface JobLauncher { + public interface JobLauncher { public JobExecution run(Job job, JobParameters jobParameters) throws JobExecutionAlreadyRunningException, JobRestartException; @@ -1041,7 +1040,7 @@ ExecutionContext ecJob = jobExecution.getExecutionContext(); bean definition, a namespace has been provided for ease of configuration: - <beans:beans xmlns="http://www.springframework.org/schema/batch" + <beans:beans xmlns="http://www.springframework.org/schema/batch" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" diff --git a/src/site/docbook/reference/index.xml b/src/site/docbook/reference/index.xml index a69039301..fadf6db3d 100644 --- a/src/site/docbook/reference/index.xml +++ b/src/site/docbook/reference/index.xml @@ -1,11 +1,11 @@ - - + Spring Batch - Reference Documentation Spring Batch - Spring Batch 3.0.0.M3 + ${version} @@ -46,8 +46,23 @@ Chris Schaefer - + + Gunnar + Hillert + + + + 2009 + 2010 + 2011 + 2012 + 2013 + 2014 + + GoPivotal, Inc. All Rights Reserved. + + Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such diff --git a/src/site/docbook/reference/job.xml b/src/site/docbook/reference/job.xml index d2ee0977f..40c4cfa0d 100644 --- a/src/site/docbook/reference/job.xml +++ b/src/site/docbook/reference/job.xml @@ -38,7 +38,7 @@ required dependencies: a name, JobRepository , and a list of Steps. - + @@ -50,7 +50,7 @@ defaults to referencing a repository with an id of 'jobRepository', which is a sensible default. However, this can be overridden explicitly: - job-repository="specialRepository" + job-repository="specialRepository" @@ -78,7 +78,7 @@ be run as part of a new JobInstance, then the restartable property may be set to 'false': - restartable="false" + restartable="false" ... ]]> @@ -87,7 +87,7 @@ restartable will cause a JobRestartException to be thrown: - SimpleJob allows for this by calling a JobListener at the appropriate time: - SimpleJob via the listeners element on the job: - + @@ -144,7 +144,7 @@ catch (JobRestartException e) { Job. If success or failure needs to be determined it can be obtained from the JobExecution: - Job with two listeners and one Step, "step1". - + @@ -216,7 +216,7 @@ catch (JobRestartException e) { of a validator is supported through the XML namespace through a child element of the job, e.g: - + ]]> @@ -279,7 +279,7 @@ catch (JobRestartException e) { to configure a job. Below is an example of a two step job configured via the JobBuilderFactory and the StepBuilderFactory. - @Configuration + @Configuration @EnableBatchProcessing @Import(DataSourceCnfiguration.class) public class AppConfig { @@ -335,17 +335,12 @@ public class AppConfig { collaborators. However, there are still a few configuration options available: - - - ]]> - - + max-varchar-length="1000"/>]]> None of the configuration options listed above are required except the id. If they are not set, the defaults shown above will be used. They @@ -376,7 +371,7 @@ public class AppConfig { platform supports it. However, this can be overridden: - isolation-level-for-create="REPEATABLE_READ"]]> @@ -385,7 +380,7 @@ public class AppConfig { using AOP: - + @@ -418,7 +413,7 @@ public class AppConfig { meta data tables is needed within the same schema, then the table prefix will need to be changed: - table-prefix="SYSTEM.TEST_"]]> Given the above changes, every query to the meta data tables will @@ -443,7 +438,7 @@ public class AppConfig { this reason, Spring batch provides an in-memory Map version of the job repository: - ]]> @@ -474,7 +469,7 @@ public class AppConfig { shortcut and use it to set the database type to the closest match: - + ]]> @@ -505,7 +500,7 @@ public class AppConfig { a JobRepository, in order to obtain an execution: - ]]> @@ -552,7 +547,7 @@ public class AppConfig { configured to allow for this scenario by configuring a TaskExecutor: - @@ -668,7 +663,7 @@ public class AppConfig { will be converted into JobParameters. An example of the XML configuration is below: - + @@ -712,7 +707,7 @@ public class AppConfig { to a number using the ExitCodeMapper interface: - HttpRequest. An example is below: - JobExplorer interface: - getJobInstances(String jobName, int start, int count); @@ -866,7 +861,7 @@ public class JobLauncherController { JobRepository, it can be easily configured via a factory bean: - ]]> Earlier in this @@ -876,7 +871,7 @@ public class JobLauncherController { JobExplorer is working with the same tables, it too needs the ability to set a prefix: - p:tablePrefix="BATCH_" ]]>
@@ -893,7 +888,7 @@ public class JobLauncherController { the framework and this is based on a simple map from job name to job instance. It is configured simply like this: - ]]> + ]]> There are two ways to populate a JobRegistry automatically: using a bean post processor and using a registrar lifecycle component. These @@ -905,7 +900,7 @@ public class JobLauncherController { This is a bean post-processor that can register all jobs as they are created: - + ]]> @@ -932,7 +927,7 @@ public class JobLauncherController { integrate jobs contributed from separate modules of an application. - + @@ -984,7 +979,7 @@ public class JobLauncherController { provides for these types of operations via the JobOperator interface: - getExecutions(long instanceId) throws NoSuchJobInstanceException; @@ -1026,7 +1021,7 @@ public class JobLauncherController { implementation of JobOperator, SimpleJobOperator, has many dependencies: - + @@ -1064,7 +1059,7 @@ public class JobLauncherController { Job to force the Job to a new instance: - Job, as shown below: - Job via the 'incrementer' attribute in the namespace: - incrementer="sampleIncrementer" + incrementer="sampleIncrementer" ... ]]> @@ -1116,8 +1111,8 @@ public class JobLauncherController { JobOperator is gracefully stopping a Job: - executions = jobOperator.getRunningExecutions("sampleJob"); -jobOperator.stop(executions.iterator().next()); ]]> + executions = jobOperator.getRunningExecutions("sampleJob"); +jobOperator.stop(executions.iterator().next());]]> The shutdown is not immediate, since there is no way to force immediate shutdown, especially if the execution is currently in diff --git a/src/site/docbook/reference/jsr-352.xml b/src/site/docbook/reference/jsr-352.xml index a55a2146d..47cd8f443 100644 --- a/src/site/docbook/reference/jsr-352.xml +++ b/src/site/docbook/reference/jsr-352.xml @@ -53,7 +53,7 @@ To use Spring dependency injection within a JSR-352 based batch job consists of configuring batch artifacts using a Spring application context as beans. Once the beans have been defined, a job can refer to them as it would any bean defined within the batch.xml. - <?xml version="1.0" encoding="UTF-8"?> + <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans @@ -73,7 +73,7 @@ </step> </job> </beans> - + The assembly of Spring contexts (imports, etc) works with JSR-352 jobs just as it would with any other Spring based application. The only difference with a JSR-352 based job is that the entry point for the @@ -82,13 +82,13 @@ To use the thread context class loader approach, all you need to do is provide the fully qualified class name as the ref. It is important to note that when using this approach or the batch.xml approach, the class referenced requires a no argument constructor which will be used to create the bean. - <?xml version="1.0" encoding="UTF-8"?> + <?xml version="1.0" encoding="UTF-8"?> <job id="fooJob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0"> <step id="step1" > <batchlet ref="io.spring.FooBatchlet" /> </step> </job> - + @@ -101,12 +101,11 @@ JSR-352 allows for properties to be defined at the Job, Step and batch artifact level by way of configuration in the JSL. Batch properties are configured at each level in the following way: - - <properties> + <properties> <property name="propertyName1" value="propertyValue1"/> <property name="propertyName2" value="propertyValue2"/> </properties> - + Properties may be configured on any batch artifact.
@@ -118,15 +117,15 @@ conversion is up to the implementing developer to perform. An javax.batch.api.chunk.ItemReader artifact could be configured with a - properties block such as the one described above and accessed as such: - public class MyItemReader extends AbstractItemReader { + properties block such as the one described above and accessed as such: + public class MyItemReader extends AbstractItemReader { @Inject @BatchProperty private String propertyName1; ... } - + The value of the field "propertyName1" will be "propertyValue1"
@@ -153,8 +152,8 @@ + #{jobParameters['unresolving.prop']}?:#{systemProperties['file.separator']} - #{jobParameters['unresolving.prop']}?:#{systemProperties['file.separator']} The left hand side of the assignment is the expected value, the right hand side is the default value. In this example, the result will resolve to a value of the system property file.separator as #{jobParameters['unresolving.prop']} is assumed to not be resolvable. If neither expressions can be @@ -190,7 +189,8 @@ ItemReader. To configure a step this way, specify the item-count (which defaults to 10) and optionally configure the checkpoint-policy as item (this is the default). - ... + + ... <step id="step1"> <chunk checkpoint-policy="item" item-count="3"> <reader ref="fooReader"/> @@ -199,6 +199,7 @@ </chunk> </step> ... + If item based checkpointing is chosen, an additional attribute time-limit is supported. This sets a time limit for how long the number of items specified has to be processed. If the timeout is reached, the chunk will complete with however many items have been read by then @@ -216,7 +217,8 @@ implementation of CheckpointAlgorithm, configure your step with the custom checkpoint-policy as shown below where fooCheckpointer refers to an implementation of CheckpointAlgorithm. - ... + + ... <step id="step1"> <chunk checkpoint-policy="custom"> <checkpoint-algorithm ref="fooCheckpointer"/> @@ -226,7 +228,6 @@ </chunk> </step> ... -
@@ -239,10 +240,10 @@ implementation is loaded via the javax.batch.runtime.BatchRuntime. Launching a JSR-352 based batch job is implemented as follows:
- + JobOperator jobOperator = BatchRuntime.getJobOperator(); long jobExecutionId = jobOperator.start("fooJob", new Properties()); - +
The above code does the following: @@ -291,9 +292,9 @@ long jobExecutionId = jobOperator.start("fooJob", new Properties()); To obtain a reference to the JobContext or StepContext within the current scope, simply use the @Inject annotation: - @Inject + @Inject JobContext jobContext; - +
@Autowire for JSR-352 contexts @@ -371,7 +372,7 @@ JobContext jobContext;
Partitioning Conceptually, partitioning in JSR-352 is the same as it is in Spring Batch. Meta-data is provided - to each slave to identify the input to be processed with the slaves reporting back to the master the + to each slave to identify the input to be processed with the slaves reporting back to the master the results upon completion. However, there are some important differences: diff --git a/src/site/docbook/reference/readersAndWriters.xml b/src/site/docbook/reference/readersAndWriters.xml index 9e2e631cb..00453db08 100644 --- a/src/site/docbook/reference/readersAndWriters.xml +++ b/src/site/docbook/reference/readersAndWriters.xml @@ -46,7 +46,7 @@ ItemReader is a basic interface for generic input operations: - public interface ItemReader<T> { + public interface ItemReader<T> { T read() throws Exception, UnexpectedInputException, ParseException; @@ -84,7 +84,7 @@ As with ItemReader, ItemWriter is a fairly generic interface: - public interface ItemWriter<T> { + public interface ItemWriter<T> { void write(List<? extends T> items) throws Exception; @@ -115,7 +115,7 @@ that contains another ItemReader. For example: - public class CompositeItemWriter<T> implements ItemWriter<T> { + public class CompositeItemWriter<T> implements ItemWriter<T> { ItemWriter<T> itemWriter; @@ -145,7 +145,7 @@ For this scenario, Spring Batch provides the ItemProcessor interface: - public interface ItemProcessor<I, O> { + public interface ItemProcessor<I, O> { O process(I item) throws Exception; } @@ -160,7 +160,7 @@ written out. An ItemProcessor can be written that performs the conversion: - public class Foo {} + public class Foo {} public class Bar { public Bar(Foo foo) {} @@ -191,7 +191,7 @@ public class BarWriter implements ItemWriter<Bar>{ provided. The FooProcessor can then be injected into a Step: - <job id="ioSampleJob"> + <job id="ioSampleJob"> <step name="step1"> <tasklet> <chunk reader="fooReader" processor="fooProcessor" writer="barWriter" @@ -211,7 +211,7 @@ public class BarWriter implements ItemWriter<Bar>{ transformed to Bar, which will be transformed to Foobar and written out: - public class Foo {} + public class Foo {} public class Bar { public Bar(Foo foo) {} @@ -244,7 +244,7 @@ public class FoobarWriter implements ItemWriter<FooBar>{ BarProcessor can be 'chained' together to give the resultant Foobar: - CompositeItemProcessor<Foo,Foobar> compositeProcessor = + CompositeItemProcessor<Foo,Foobar> compositeProcessor = new CompositeItemProcessor<Foo,Foobar>(); List itemProcessors = new ArrayList(); itemProcessors.add(new FooTransformer()); @@ -254,7 +254,7 @@ compositeProcessor.setDelegates(itemProcessors); Just as with the previous example, the composite processor can be configured into the Step: - <job id="ioSampleJob"> + <job id="ioSampleJob"> <step name="step1"> <tasklet> <chunk reader="fooReader" processor="compositeProcessor" writer="foobarWriter" @@ -323,7 +323,7 @@ compositeProcessor.setDelegates(itemProcessors); writers need to be opened, closed, and require a mechanism for persisting state: - public interface ItemStream { + public interface ItemStream { void open(ExecutionContext executionContext) throws ItemStreamException; @@ -378,7 +378,7 @@ compositeProcessor.setDelegates(itemProcessors); are not known to the Step, they need to be injected as listeners or streams (or both if appropriate): - <job id="ioSampleJob"> + <job id="ioSampleJob"> <step name="step1"> <tasklet> <chunk reader="fooReader" processor="fooProcessor" writer="compositeItemWriter" @@ -427,7 +427,7 @@ compositeProcessor.setDelegates(itemProcessors); fields so that the fields may be accessed either by index or name as patterned after ResultSet: - String[] tokens = new String[]{"foo", "1", "true"}; + String[] tokens = new String[]{"foo", "1", "true"}; FieldSet fs = new DefaultFieldSet(tokens); String name = fs.readString(0); int value = fs.readInt(1); @@ -461,7 +461,9 @@ boolean booleanValue = fs.readBoolean(2); Framework, Chapter 5.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"); + 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 @@ -580,11 +582,13 @@ boolean booleanValue = fs.readBoolean(2); level construct such as ResultSet and returns an Object, flat file processing requires the same construct to convert a String line into an - Object:public interface LineMapper<T> { + Object: + + public interface LineMapper<T> { T mapLine(String line, int lineNumber) throws Exception; -} +} The basic contract is that, given the current line and the line number with which it is associated, the mapper should return a @@ -610,7 +614,7 @@ boolean booleanValue = fs.readBoolean(2); FieldSet. In Spring Batch, this interface is the LineTokenizer: - public interface LineTokenizer { + public interface LineTokenizer { FieldSet tokenize(String line); @@ -659,7 +663,7 @@ boolean booleanValue = fs.readBoolean(2); LineTokenizer to translate a line of data from a resource into an object of the desired type: - public interface FieldSetMapper<T> { + public interface FieldSetMapper<T> { T mapFieldSet(FieldSet fieldSet); @@ -706,7 +710,7 @@ boolean booleanValue = fs.readBoolean(2); DefaultLineMapper represents the behavior most users will need: - public class DefaultLineMapper<T> implements LineMapper<T>, InitializingBean { + public class DefaultLineMapper<T> implements LineMapper<T>, InitializingBean { private LineTokenizer tokenizer; @@ -737,16 +741,20 @@ boolean booleanValue = fs.readBoolean(2); 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 + 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 { + Player domain object: + + public class Player implements Serializable { private String ID; private String lastName; @@ -763,15 +771,14 @@ boolean booleanValue = fs.readBoolean(2); } // 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> { + protected static class PlayerFieldSetMapper implements FieldSetMapper<Player> { public Player mapFieldSet(FieldSet fieldSet) { Player player = new Player(); @@ -784,13 +791,13 @@ boolean booleanValue = fs.readBoolean(2); return player; } -} +} The file can then be read by correctly constructing a FlatFileItemReader and calling read: - FlatFileItemReader<Player> itemReader = new FlatFileItemReader<Player>(); + FlatFileItemReader<Player> itemReader = new FlatFileItemReader<Player>(); itemReader.setResource(new FileSystemResource("resources/players.csv")); //DelimitedLineTokenizer defaults to comma as its delimiter LineMapper<Player> lineMapper = new DefaultLineMapper<Player>(); @@ -817,12 +824,12 @@ Player player = itemReader.read(); readability of the mapping function. First, the column names of all fields in the flat file are injected into the tokenizer: - tokenizer.setNames(new String[] {"ID", "lastName","firstName","position","birthYear","debutYear"}); + tokenizer.setNames(new String[] {"ID", "lastName","firstName","position","birthYear","debutYear"}); A FieldSetMapper can use this information as follows: - public class PlayerMapper implements FieldSetMapper<Player> { + public class PlayerMapper implements FieldSetMapper<Player> { public Player mapFieldSet(FieldSet fs) { if(fs == null){ @@ -839,7 +846,7 @@ Player player = itemReader.read(); return player; } -} +}
@@ -855,7 +862,7 @@ Player player = itemReader.read(); BeanWrapperFieldSetMapper configuration looks like the following: - <bean id="fieldSetMapper" + <bean id="fieldSetMapper" class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"> <property name="prototypeBeanName" value="player" /> </bean> @@ -916,7 +923,7 @@ UK21341EAH4521535.11customer5 FixedLengthLineTokenizer, each of these lengths must be provided in the form of ranges: - <bean id="fixedLengthLineTokenizer" + <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" /> @@ -969,7 +976,7 @@ LINEB;2134776319DEF422.99M005LI LineTokenizers and patterns to FieldSetMappers to be configured: - <bean id="orderFileLineMapper" + <bean id="orderFileLineMapper" class="org.spr...PatternMatchingCompositeLineMapper"> <property name="tokenizers"> <map> @@ -1006,7 +1013,7 @@ LINEB;2134776319DEF422.99M005LI ("*") can serve as a default by matching any line not matched by any other pattern. - <entry key="*" value-ref="defaultLineTokenizer" /> + <entry key="*" value-ref="defaultLineTokenizer" /> There is also a PatternMatchingCompositeLineTokenizer that can @@ -1051,9 +1058,9 @@ LINEB;2134776319DEF422.99M005LI contains the number of tokens encountered, and the number expected: - tokenizer.setNames(new String[] {"A", "B", "C", "D"}); + tokenizer.setNames(new String[] {"A", "B", "C", "D"}); -try{ +try { tokenizer.tokenize("a,b,c"); } catch(IncorrectTokenCountException e){ @@ -1076,7 +1083,7 @@ catch(IncorrectTokenCountException e){ line length doesn't add up to the widest value of this column, an exception is thrown: - tokenizer.setColumns(new Range[] { new Range(1, 5), + tokenizer.setColumns(new Range[] { new Range(1, 5), new Range(6, 10), new Range(11, 15) }); try { @@ -1100,7 +1107,7 @@ catch (IncorrectLineLengthException ex) { For this reason, validation of line length can be turned off via the 'strict' property: - tokenizer.setColumns(new Range[] { new Range(1, 5), new Range(6, 10) }); + tokenizer.setColumns(new Range[] { new Range(1, 5), new Range(6, 10) }); tokenizer.setStrict(false); FieldSet tokens = tokenizer.tokenize("12345"); assertEquals("12345", tokens.readString(0)); @@ -1134,7 +1141,7 @@ assertEquals("", tokens.readString(1)); In Spring Batch this is the LineAggregator: - public interface LineAggregator<T> { + public interface LineAggregator<T> { public String aggregate(T item); @@ -1157,7 +1164,7 @@ assertEquals("", tokens.readString(1)); simply assumes that the object is already a string, or that its string representation is acceptable for writing: - public class PassThroughLineAggregator<T> implements LineAggregator<T> { + public class PassThroughLineAggregator<T> implements LineAggregator<T> { public String aggregate(T item) { return item.toString(); @@ -1196,13 +1203,13 @@ assertEquals("", tokens.readString(1)); FlatFileItemWriter expresses this in code: - public void write(T item) throws Exception { + public void write(T item) throws Exception { write(lineAggregator.aggregate(item) + LINE_SEPARATOR); } A simple configuration would look like the following: - <bean id="itemWriter" class="org.spr...FlatFileItemWriter"> + <bean id="itemWriter" class="org.spr...FlatFileItemWriter"> <property name="resource" value="file:target/test-outputs/output.txt" /> <property name="lineAggregator"> <bean class="org.spr...PassThroughLineAggregator"/> @@ -1257,7 +1264,7 @@ assertEquals("", tokens.readString(1)); FieldExtractor must be written to accomplish the task of turning the item into an array: - public interface FieldExtractor<T> { + public interface FieldExtractor<T> { Object[] extract(T item); @@ -1293,7 +1300,7 @@ assertEquals("", tokens.readString(1)); BeanWrapperFieldExtractor provides just this type of functionality: - BeanWrapperFieldExtractor<Name> extractor = new BeanWrapperFieldExtractor<Name>(); + BeanWrapperFieldExtractor<Name> extractor = new BeanWrapperFieldExtractor<Name>(); extractor.setNames(new String[] { "first", "last", "born" }); String first = "Alan"; @@ -1328,7 +1335,7 @@ assertEquals(born, values[2]); writes out a simple domain object that represents a credit to a customer account: - public class CustomerCredit { + public class CustomerCredit { private int id; private String name; @@ -1341,7 +1348,7 @@ assertEquals(born, values[2]); FieldExtractor interface must be provided, along with the delimiter to use: - <bean id="itemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter"> + <bean id="itemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter"> <property name="resource" ref="outputResource" /> <property name="lineAggregator"> <bean class="org.spr...DelimitedLineAggregator"> @@ -1372,7 +1379,7 @@ assertEquals(born, values[2]); Using the same CustomerCredit domain object described above, it can be configured as follows: - <bean id="itemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter"> + <bean id="itemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter"> <property name="resource" ref="outputResource" /> <property name="lineAggregator"> <bean class="org.spr...FormatterLineAggregator"> @@ -1389,7 +1396,7 @@ assertEquals(born, values[2]); Most of the above example should look familiar. However, the value of the format property is new: - <property name="format" value="%-9s%-2.0f" /> + <property name="format" value="%-9s%-2.0f" /> The underlying implementation is built using the same Formatter added as part of Java 5. The Java @@ -1498,7 +1505,7 @@ assertEquals(born, values[2]); stream. First, lets examine a set of XML records that the StaxEventItemReader can process. - <?xml version="1.0" encoding="UTF-8"?> + <?xml version="1.0" encoding="UTF-8"?> <records> <trade xmlns="http://springframework.org/batch/sample/io/oxm/domain"> <isin>XYZ0001</isin> @@ -1518,7 +1525,7 @@ assertEquals(born, values[2]); <price>99.99</price> <customer>Customer3</customer> </trade> -</records> +</records> To be able to process the XML records the following is needed: @@ -1540,12 +1547,11 @@ assertEquals(born, values[2]); - <bean id="itemReader" class="org.springframework.batch.item.xml.StaxEventItemReader"> + <bean id="itemReader" class="org.springframework.batch.item.xml.StaxEventItemReader"> <property name="fragmentRootElementName" value="trade" /> <property name="resource" value="data/iosample/input/input.xml" /> <property name="unmarshaller" ref="tradeMarshaller" /> -</bean> - +</bean> Notice that in this example we have chosen to use an XStreamMarshaller which accepts an alias passed @@ -1556,7 +1562,7 @@ assertEquals(born, values[2]); the map. In the configuration file we can use a Spring configuration utility to describe the required alias as follows: - <bean id="tradeMarshaller" + <bean id="tradeMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller"> <property name="aliases"> <util:map id="aliases"> @@ -1566,7 +1572,7 @@ assertEquals(born, values[2]); <entry key="name" value="java.lang.String" /> </util:map> </property> -</bean> +</bean> 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 @@ -1580,7 +1586,7 @@ assertEquals(born, values[2]); Java code which uses the injection provided by the Spring configuration: - StaxEventItemReader xmlStaxEventItemReader = new StaxEventItemReader() + StaxEventItemReader xmlStaxEventItemReader = new StaxEventItemReader() Resource resource = new ByteArrayResource(xmlResource.getBytes()) Map aliases = new HashMap(); @@ -1606,7 +1612,7 @@ while (hasNext) { else { System.out.println(credit); } -} +}
@@ -1624,7 +1630,7 @@ while (hasNext) { MarshallingEventWriterSerializer. The Spring configuration for this setup looks as follows: - <bean id="itemWriter" class="org.springframework.batch.item.xml.StaxEventItemWriter"> + <bean id="itemWriter" class="org.springframework.batch.item.xml.StaxEventItemWriter"> <property name="resource" ref="outputResource" /> <property name="marshaller" ref="customerCreditMarshaller" /> <property name="rootTagName" value="customers" /> @@ -1637,7 +1643,7 @@ while (hasNext) { 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: - <bean id="customerCreditMarshaller" + <bean id="customerCreditMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller"> <property name="aliases"> <util:map id="aliases"> @@ -1653,7 +1659,7 @@ while (hasNext) { all of the points discussed, demonstrating the programmatic setup of the required properties: - StaxEventItemWriter staxItemWriter = new StaxEventItemWriter() + StaxEventItemWriter staxItemWriter = new StaxEventItemWriter() FileSystemResource resource = new FileSystemResource("data/outputFile.xml") Map aliases = new HashMap(); @@ -1693,7 +1699,7 @@ staxItemWriter.write(trade); MuliResourceItemReader can be used to read in both files by using wildcards: - <bean id="multiResourceReader" class="org.spr...MultiResourceItemReader"> + <bean id="multiResourceReader" class="org.spr...MultiResourceItemReader"> <property name="resources" value="classpath:data/input/file-*.txt" /> <property name="delegate" ref="flatFileItemReader" /> </bean> @@ -1776,7 +1782,7 @@ staxItemWriter.write(trade); DataSource. The following database schema will be used as an example: - CREATE TABLE CUSTOMER ( + CREATE TABLE CUSTOMER ( ID BIGINT IDENTITY PRIMARY KEY, NAME VARCHAR(45), CREDIT FLOAT @@ -1787,7 +1793,7 @@ staxItemWriter.write(trade); interface to map a CustomerCredit object: - public class CustomerCreditRowMapper implements RowMapper { + public class CustomerCreditRowMapper implements RowMapper { public static final String ID_COLUMN = "id"; public static final String NAME_COLUMN = "name"; @@ -1813,7 +1819,7 @@ staxItemWriter.write(trade); CUSTOMER database. The first example will be using JdbcTemplate: - //For simplicity sake, assume a dataSource has already been obtained + //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()); @@ -1827,7 +1833,7 @@ List customerCredits = jdbcTemplate.query("SELECT ID, NAME, CREDIT from CUSTOMER contrast this with the approach of the JdbcCursorItemReader: - JdbcCursorItemReader itemReader = new JdbcCursorItemReader(); + JdbcCursorItemReader itemReader = new JdbcCursorItemReader(); itemReader.setDataSource(dataSource); itemReader.setSql("SELECT ID, NAME, CREDIT from CUSTOMER"); itemReader.setRowMapper(new CustomerCreditRowMapper()); @@ -1855,7 +1861,7 @@ itemReader.close(executionContext); configured for injection into a Spring Batch Step: - <bean id="itemReader" class="org.spr...JdbcCursorItemReader"> + <bean id="itemReader" class="org.spr...JdbcCursorItemReader"> <property name="dataSource" ref="dataSource"/> <property name="sql" value="select ID, NAME, CREDIT from CUSTOMER"/> <property name="rowMapper"> @@ -2000,7 +2006,7 @@ itemReader.close(executionContext); configuration using the same 'customer credit' example as the JDBC reader: - HibernateCursorItemReader itemReader = new HibernateCursorItemReader(); + HibernateCursorItemReader itemReader = new HibernateCursorItemReader(); itemReader.setQueryString("from CustomerCredit"); //For simplicity sake, assume sessionFactory already obtained. itemReader.setSessionFactory(sessionFactory); @@ -2026,7 +2032,7 @@ itemReader.close(executionContext); JdbcCursorItemReader, configuration is straightforward: - <bean id="itemReader" + <bean id="itemReader" class="org.springframework.batch.item.database.HibernateCursorItemReader"> <property name="sessionFactory" ref="sessionFactory" /> <property name="queryString" value="from CustomerCredit" /> @@ -2062,7 +2068,7 @@ itemReader.close(executionContext); Below is a basic example configuration using the same 'customer credit' example as earlier: - <bean id="reader" class="org.springframework.batch.item.database.StoredProcedureItemReader"> + <bean id="reader" class="o.s.batch.item.database.StoredProcedureItemReader"> <property name="dataSource" ref="dataSource"/> <property name="procedureName" value="sp_customer_credit"/> <property name="rowMapper"> @@ -2079,7 +2085,7 @@ itemReader.close(executionContext); returned ref-cursor. Here is an example where the first parameter is the returned ref-cursor: - <bean id="reader" class="org.springframework.batch.item.database.StoredProcedureItemReader"> + <bean id="reader" class="o.s.batch.item.database.StoredProcedureItemReader"> <property name="dataSource" ref="dataSource"/> <property name="procedureName" value="sp_customer_credit"/> <property name="refCursorPosition" value="1"/> @@ -2094,7 +2100,7 @@ itemReader.close(executionContext); true. It defaults to false. Here is what that would look like: - <bean id="reader" class="org.springframework.batch.item.database.StoredProcedureItemReader"> + <bean id="reader" class="o.s.batch.item.database.StoredProcedureItemReader"> <property name="dataSource" ref="dataSource"/> <property name="procedureName" value="sp_customer_credit"/> <property name="function" value="true"/> @@ -2115,7 +2121,7 @@ itemReader.close(executionContext); the out parameter that returns the ref-cursor, the second and third are in parameters that takes a value of type INTEGER: - <bean id="reader" class="org.springframework.batch.item.database.StoredProcedureItemReader"> + <bean id="reader" class="o.s.batch.item.database.StoredProcedureItemReader"> <property name="dataSource" ref="dataSource"/> <property name="procedureName" value="spring.cursor_func"/> <property name="parameters"> @@ -2194,7 +2200,7 @@ itemReader.close(executionContext); Below is an example configuration using a similar 'customer credit' example as the cursor based ItemReaders above: - <bean id="itemReader" class="org.spr...JdbcPagingItemReader"> + <bean id="itemReader" class="org.spr...JdbcPagingItemReader"> <property name="dataSource" ref="dataSource"/> <property name="queryProvider"> <bean class="org.spr...SqlPagingQueryProviderFactoryBean"> @@ -2250,7 +2256,7 @@ itemReader.close(executionContext); is an example configuration using the same 'customer credit' example as the JDBC reader above: - <bean id="itemReader" class="org.spr...JpaPagingItemReader"> + <bean id="itemReader" class="org.spr...JpaPagingItemReader"> <property name="entityManagerFactory" ref="entityManagerFactory"/> <property name="queryString" value="select c from CustomerCredit c"/> <property name="pageSize" value="1000"/> @@ -2280,7 +2286,7 @@ itemReader.close(executionContext); IbatisPagingItemReader reading CustomerCredits as in the examples above: - <bean id="itemReader" class="org.spr...IbatisPagingItemReader"> + <bean id="itemReader" class="org.spr...IbatisPagingItemReader"> <property name="sqlMapClient" ref="sqlMapClient"/> <property name="queryId" value="getPagedCustomerCredits"/> <property name="pageSize" value="1000"/> @@ -2291,7 +2297,7 @@ itemReader.close(executionContext); Here is an example of what that query should look like for MySQL. - <select id="getPagedCustomerCredits" resultMap="customerCreditResult"> + <select id="getPagedCustomerCredits" resultMap="customerCreditResult"> select id, name, credit from customer order by id asc LIMIT #_skiprows#, #_pagesize# </select> @@ -2303,14 +2309,13 @@ itemReader.close(executionContext); is an example for Oracle (unfortunately we need to use CDATA for some operators since this belongs in an XML document): - <select id="getPagedCustomerCredits" resultMap="customerCreditResult"> + <select id="getPagedCustomerCredits" resultMap="customerCreditResult"> select * from ( select * from ( select t.id, t.name, t.credit, ROWNUM ROWNUM_ from customer t order by id )) where ROWNUM_ <![CDATA[ > ]]> ( #_page# * #_pagesize# ) ) where ROWNUM <![CDATA[ <= ]]> #_pagesize# - </select> - +</select>
@@ -2407,7 +2412,7 @@ itemReader.close(executionContext);
standard Spring method invoking the delegate 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"> + <bean id="itemReader" class="org.springframework.batch.item.adapter.ItemReaderAdapter"> <property name="targetObject" ref="fooService" /> <property name="targetMethod" value="generateFoo" /> </bean> @@ -2423,7 +2428,7 @@ itemReader.close(executionContext); ItemWriter. The ItemWriter implementation is equally as simple: - <bean id="itemWriter" class="org.springframework.batch.item.adapter.ItemWriterAdapter"> + <bean id="itemWriter" class="org.springframework.batch.item.adapter.ItemWriterAdapter"> <property name="targetObject" ref="fooService" /> <property name="targetMethod" value="processFoo" /> </bean> @@ -2452,7 +2457,7 @@ itemReader.close(executionContext); rather provides a very simple interface that can be implemented by any number of frameworks: - public interface Validator { + public interface Validator { void validate(Object value) throws ValidationException; @@ -2463,7 +2468,7 @@ itemReader.close(executionContext); it is valid. Spring Batch provides an out of the box ItemProcessor: - <bean class="org.springframework.batch.item.validator.ValidatingItemProcessor"> + <bean class="org.springframework.batch.item.validator.ValidatingItemProcessor"> <property name="validator" ref="validator" /> </bean> @@ -2516,7 +2521,7 @@ itemReader.close(executionContext); restart. For this reason, all readers and writers include the 'saveState' property: - <bean id="playerSummarizationSource" class="org.spr...JdbcCursorItemReader"> + <bean id="playerSummarizationSource" class="org.spr...JdbcCursorItemReader"> <property name="dataSource" ref="dataSource" /> <property name="rowMapper"> <bean class="org.springframework.batch.sample.PlayerSummaryMapper" /> @@ -2563,7 +2568,7 @@ itemReader.close(executionContext); basic contract of ItemReader, read: - public class CustomItemReader<T> implements ItemReader<T>{ + public class CustomItemReader<T> implements ItemReader<T>{ List<T> items; @@ -2586,7 +2591,7 @@ itemReader.close(executionContext); returns null, thus satisfying the most basic requirements of an ItemReader, as illustrated below: - List<String> items = new ArrayList<String>(); + List<String> items = new ArrayList<String>(); items.add("1"); items.add("2"); items.add("3"); @@ -2616,7 +2621,7 @@ assertNull(itemReader.read()); If you do need to store state, then the ItemStream interface should be used: - public class CustomItemReader<T> implements ItemReader<T>, ItemStream { + public class CustomItemReader<T> implements ItemReader<T>, ItemStream { List<T> items; int currentIndex = 0; @@ -2663,7 +2668,7 @@ assertNull(itemReader.read()); fairly trivial example, but it still meets the general contract: - ExecutionContext executionContext = new ExecutionContext(); + ExecutionContext executionContext = new ExecutionContext(); ((ItemStream)itemReader).open(executionContext); assertEquals("1", itemReader.read()); ((ItemStream)itemReader).update(executionContext); @@ -2709,7 +2714,7 @@ assertEquals("2", itemReader.read()); List will be used in order to keep the example as simple as possible: - public class CustomItemWriter<T> implements ItemWriter<T> { + public class CustomItemWriter<T> implements ItemWriter<T> { List<T> output = TransactionAwareProxyFactory.createTransactionalList(); diff --git a/src/site/docbook/reference/repeat.xml b/src/site/docbook/reference/repeat.xml index 0aea0fb85..1aff2ffd1 100644 --- a/src/site/docbook/reference/repeat.xml +++ b/src/site/docbook/reference/repeat.xml @@ -14,18 +14,20 @@ The RepeatOperations interface looks like this: - public interface RepeatOperations { + public interface RepeatOperations { RepeatStatus iterate(RepeatCallback callback) throws RepeatException; -}The callback is a simple interface that allows you to insert +} + The callback is a simple interface that allows you to insert some business logic to be repeated: - public interface RepeatCallback { + public interface RepeatCallback { RepeatStatus doInIteration(RepeatContext context) throws Exception; -}The callback is executed repeatedly until the implementation +} + The callback is executed repeatedly until the implementation decides that the iteration should end. The return value in these interfaces is an enumeration that can either be RepeatStatus.CONTINUABLE or @@ -42,7 +44,7 @@ RepeatOperations is RepeatTemplate. It could be used like this: - RepeatTemplate template = new RepeatTemplate(); + RepeatTemplate template = new RepeatTemplate(); template.setCompletionPolicy(new FixedChunkSizeCompletionPolicy(2)); @@ -162,12 +164,13 @@ template.iterate(new RepeatCallback() { ExceptionHandler which can decide whether or not to re-throw the exception. - public interface ExceptionHandler { + public interface ExceptionHandler { void handleException(RepeatContext context, Throwable throwable) throws RuntimeException; -}A common use case is to count the number of exceptions of a +} + A common use case is to count the number of exceptions of a given type, and fail when a limit is reached. For this purpose Spring Batch provides the SimpleLimitExceptionHandler and slightly more flexible @@ -200,17 +203,14 @@ template.iterate(new RepeatCallback() { The interface looks like this: - public interface RepeatListener { + public interface RepeatListener { void before(RepeatContext context); - void after(RepeatContext context, RepeatStatus result); - void open(RepeatContext context); - void onError(RepeatContext context, Throwable e); - void close(RepeatContext context); -}The open and +} + The open and close callbacks come before and after the entire iteration. before, after and onError apply to the individual @@ -258,7 +258,7 @@ template.iterate(new RepeatCallback() { processMessage (for more detail on how to configure AOP interceptors see the Spring User Guide): - <aop:config> + <aop:config> <aop:pointcut id="transactional" expression="execution(* com..*Service.processMessage(..))" /> <aop:advisor pointcut-ref="transactional" diff --git a/src/site/docbook/reference/retry.xml b/src/site/docbook/reference/retry.xml index eb2677c72..64f6c3f4e 100644 --- a/src/site/docbook/reference/retry.xml +++ b/src/site/docbook/reference/retry.xml @@ -23,7 +23,7 @@ strategy. The RetryOperations interface looks like this: - public interface RetryOperations { + public interface RetryOperations { <T> T execute(RetryCallback<T> retryCallback) throws Exception; @@ -36,14 +36,16 @@ <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback, RetryState retryState) throws Exception; -}The basic callback is a simple interface that allows you to +} + The basic callback is a simple interface that allows you to insert some business logic to be retried: - public interface RetryCallback<T> { + public interface RetryCallback<T> { T doWithRetry(RetryContext context) throws Throwable; -}The callback is executed and if it fails (by throwing an +} + The callback is executed and if it fails (by throwing an Exception), it will be retried until either it is successful, or the implementation decides to abort. There are a number of overloaded execute methods in the @@ -56,7 +58,7 @@ RetryOperations is RetryTemplate. It could be used like this - RetryTemplate template = new RetryTemplate(); + RetryTemplate template = new RetryTemplate(); TimeoutRetryPolicy policy = new TimeoutRetryPolicy(); policy.setTimeout(30000L); @@ -99,7 +101,7 @@ Foo result = template.execute(new RetryCallback<Foo>() { feature clients just pass in the callbacks together to the same method, for example: - Foo foo = template.execute(new RetryCallback<Foo>() { + Foo foo = template.execute(new RetryCallback<Foo>() { public Foo doWithRetry(RetryContext context) { // business logic here }, @@ -107,7 +109,8 @@ Foo result = template.execute(new RetryCallback<Foo>() { Foo recover(RetryContext context) throws Exception { // recover logic here } -});If the business logic does not succeed before the template +}); + If the business logic does not succeed before the template decides to abort, then the client is given the chance to do some alternate processing through the recovery callback. @@ -235,7 +238,7 @@ Foo result = template.execute(new RetryCallback<Foo>() { this list overrides the retryable list so that it can be used to give finer control over the retry behavior: - SimpleRetryPolicy policy = new SimpleRetryPolicy(); + SimpleRetryPolicy policy = new SimpleRetryPolicy(); // Set the max retry attempts policy.setMaxAttempts(5); // Retry on all exceptions (this is the default) @@ -277,14 +280,15 @@ template.execute(new RetryCallback<Foo>() { RetryTemplate can pause execution according to the BackoffPolicy in place. - public interface BackoffPolicy { + public interface BackoffPolicy { BackOffContext start(RetryContext context); void backOff(BackOffContext backOffContext) throws BackOffInterruptedException; -}A BackoffPolicy is free to implement +} + A BackoffPolicy is free to implement the backOff in any way it chooses. The policies provided by Spring Batch out of the box all use Object.wait(). A common use case is to backoff with an exponentially increasing wait period, to avoid two retries @@ -307,14 +311,15 @@ template.execute(new RetryCallback<Foo>() { The interface looks like this: - public interface RetryListener { + public interface RetryListener { void open(RetryContext context, RetryCallback<T> callback); void onError(RetryContext context, RetryCallback<T> callback, Throwable e); void close(RetryContext context, RetryCallback<T> callback, Throwable e); -}The open and +} + The open and close callbacks come before and after the entire retry in the simplest case and onError applies to the individual RetryCallback calls. The @@ -345,7 +350,7 @@ template.execute(new RetryCallback<Foo>() { remoteCall (for more detail on how to configure AOP interceptors see the Spring User Guide): - <aop:config> + <aop:config> <aop:pointcut id="transactional" expression="execution(* com..*Service.remoteCall(..))" /> <aop:advisor pointcut-ref="transactional" diff --git a/src/site/docbook/reference/scalability.xml b/src/site/docbook/reference/scalability.xml index 6cbc734b0..d4d83d140 100644 --- a/src/site/docbook/reference/scalability.xml +++ b/src/site/docbook/reference/scalability.xml @@ -46,7 +46,7 @@ TaskExecutor to your Step configuration, e.g. as an attribute of the tasklet: - <step id="loading"> + <step id="loading"> <tasklet task-executor="taskExecutor">...</tasklet> </step> @@ -69,10 +69,10 @@ configuration which defaults to 4. You may need to increase this to ensure that a thread pool is fully utilised, e.g. - <step id="loading"> <tasklet + <step id="loading"> <tasklet task-executor="taskExecutor" throttle-limit="20">...</tasklet> - </step> +</step> Note also that there may be limits placed on concurrency by any pooled resources used in your step, such as @@ -91,7 +91,7 @@ indicator (see ) to keep track of items that have been processed in a database input table. - Spring Batch provides some implementations of + Spring Batch provides some implementations of ItemWriter and ItemReader. Usually they say in the Javadocs if they are thread safe or not, or what you have to do to @@ -117,7 +117,7 @@ (step1,step2) in parallel with step3, you could configure a flow like this: - <job id="job1"> + <job id="job1"> <split id="split1" task-executor="taskExecutor" next="step4"> <flow> <step id="step1" parent="s1" next="step2"/> @@ -130,7 +130,7 @@ <step id="step4" parent="s4"/> </job> -<beans:bean id="taskExecutor" class="org.spr...SimpleAsyncTaskExecutor"/> +<beans:bean id="taskExecutor" class="org.spr...SimpleAsyncTaskExecutor"/> The configurable "task-executor" attribute is used to specify which TaskExecutor implementation should be used to execute the individual @@ -228,11 +228,11 @@ the PartitionStep is shown driving the execution. The PartitionStep configuration looks like this: - <step id="step1.master"> + <step id="step1.master"> <partition step="step1" partitioner="partitioner"> <handler grid-size="10" task-executor="taskExecutor"/> </partition> -</step> +</step> Similar to the multi-threaded step's throttle-limit attribute, the grid-size attribute prevents the task executor from @@ -279,7 +279,7 @@ default for a step configured with the XML namespace as above. It can also be configured explicitly like this: - <step id="step1.master"> + <step id="step1.master"> <partition step="step1" handler="handler"/> </step> @@ -287,7 +287,7 @@ <property name="taskExecutor" ref="taskExecutor"/> <property name="step" ref="step1" /> <property name="gridSize" value="10" /> -</bean> +</bean> The gridSize determines the number of separate step executions to create, so it can be matched to the size of the @@ -309,7 +309,7 @@ execution contexts as input parameters for new step executions only (no need to worry about restarts). It has a single method: - public interface Partitioner { + public interface Partitioner { Map<String, ExecutionContext> partition(int gridSize); } @@ -400,7 +400,7 @@ Then the file name can be bound to a step using late binding to the execution context: - <bean id="itemReader" scope="step" + <bean id="itemReader" scope="step" class="org.spr...MultiResourceItemReader"> <property name="resource" value="#{stepExecutionContext[fileName]}/*"/> </bean> diff --git a/src/site/docbook/reference/schema-appendix.xml b/src/site/docbook/reference/schema-appendix.xml index 91fcbc755..d9f0f8f10 100644 --- a/src/site/docbook/reference/schema-appendix.xml +++ b/src/site/docbook/reference/schema-appendix.xml @@ -79,14 +79,14 @@ requiring it, sequences were used. Each variation of the schema will contain some form of the following: - CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ; + CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ; CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ; CREATE SEQUENCE BATCH_JOB_SEQ; Many database vendors don't support sequences. In these cases, work-arounds are used, such as the following for MySQL: - CREATE TABLE BATCH_STEP_EXECUTION_SEQ (ID BIGINT NOT NULL) type=MYISAM; + CREATE TABLE BATCH_STEP_EXECUTION_SEQ (ID BIGINT NOT NULL) type=MYISAM; INSERT INTO BATCH_STEP_EXECUTION_SEQ values(0); CREATE TABLE BATCH_JOB_EXECUTION_SEQ (ID BIGINT NOT NULL) type=MYISAM; INSERT INTO BATCH_JOB_EXECUTION_SEQ values(0); @@ -108,10 +108,10 @@ INSERT INTO BATCH_JOB_SEQ values(0); hierarchy. The following generic DDL statement is used to create it: - CREATE TABLE BATCH_JOB_INSTANCE ( - JOB_INSTANCE_ID BIGINT PRIMARY KEY , - VERSION BIGINT, - JOB_NAME VARCHAR(100) NOT NULL , + CREATE TABLE BATCH_JOB_INSTANCE ( + JOB_INSTANCE_ID BIGINT PRIMARY KEY , + VERSION BIGINT, + JOB_NAME VARCHAR(100) NOT NULL , JOB_KEY VARCHAR(2500) ); @@ -152,12 +152,12 @@ INSERT INTO BATCH_JOB_SEQ values(0); The BATCH_JOB_EXECUTION_PARAMS table holds all information relevant to the JobParameters object. It contains 0 or more key/value pairs passed to a Job and serve as a record of the parameters - a job was run with. For each parameter that contributes to the generation of a job's identity, + a job was run with. For each parameter that contributes to the generation of a job's identity, the IDENTIFYING flag is set to true. It should be noted that the table has been denormalized. Rather than creating a separate table for each type, there is one table with a column indicating the type: - CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( JOB_EXECUTION_ID BIGINT NOT NULL , TYPE_CD VARCHAR(6) NOT NULL , KEY_NAME VARCHAR(100) NOT NULL , @@ -225,12 +225,12 @@ INSERT INTO BATCH_JOB_SEQ values(0); Job is run there will always be a new JobExecution, and a new row in this table: - CREATE TABLE BATCH_JOB_EXECUTION ( + CREATE TABLE BATCH_JOB_EXECUTION ( JOB_EXECUTION_ID BIGINT PRIMARY KEY , - VERSION BIGINT, + VERSION BIGINT, JOB_INSTANCE_ID BIGINT NOT NULL, CREATE_TIME TIMESTAMP NOT NULL, - START_TIME TIMESTAMP DEFAULT NULL, + START_TIME TIMESTAMP DEFAULT NULL, END_TIME TIMESTAMP DEFAULT NULL, STATUS VARCHAR(10), EXIT_CODE VARCHAR(20), @@ -313,22 +313,22 @@ INSERT INTO BATCH_JOB_SEQ values(0); least one entry per Step for each JobExecution created: - CREATE TABLE BATCH_STEP_EXECUTION ( + CREATE TABLE BATCH_STEP_EXECUTION ( STEP_EXECUTION_ID BIGINT PRIMARY KEY , - VERSION BIGINT NOT NULL, + VERSION BIGINT NOT NULL, STEP_NAME VARCHAR(100) NOT NULL, JOB_EXECUTION_ID BIGINT NOT NULL, - START_TIME TIMESTAMP NOT NULL , - END_TIME TIMESTAMP DEFAULT NULL, + START_TIME TIMESTAMP NOT NULL , + END_TIME TIMESTAMP DEFAULT NULL, STATUS VARCHAR(10), - COMMIT_COUNT BIGINT , + COMMIT_COUNT BIGINT , READ_COUNT BIGINT , FILTER_COUNT BIGINT , WRITE_COUNT BIGINT , READ_SKIP_COUNT BIGINT , WRITE_SKIP_COUNT BIGINT , PROCESS_SKIP_COUNT BIGINT , - ROLLBACK_COUNT BIGINT , + ROLLBACK_COUNT BIGINT , EXIT_CODE VARCHAR(20) , EXIT_MESSAGE VARCHAR(2500) , LAST_UPDATED TIMESTAMP, @@ -456,7 +456,7 @@ INSERT INTO BATCH_JOB_SEQ values(0); JobInstance can 'start from where it left off'. - CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( + CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( JOB_EXECUTION_ID BIGINT PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, SERIALIZED_CONTEXT CLOB, @@ -497,7 +497,7 @@ INSERT INTO BATCH_JOB_SEQ values(0); JobInstance can 'start from where it left off'. - CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( + CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( STEP_EXECUTION_ID BIGINT PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, SERIALIZED_CONTEXT CLOB, @@ -562,8 +562,8 @@ INSERT INTO BATCH_JOB_SEQ values(0); If you are using multi-byte character sets (e.g. Chines or Cyrillic) in your business processing, then those characters might need to be - persisted in the Spring Batch schema. Many users find that - simply changing the schema to double the length of the VARCHAR + persisted in the Spring Batch schema. Many users find that + simply changing the schema to double the length of the VARCHAR columns is enough. Others prefer to configure the JobRepository with max-varchar-length half the value of the VARCHAR column length is enough. Some users have also reported that they use NVARCHAR in place of VARCHAR diff --git a/src/site/docbook/reference/spring-batch-integration.xml b/src/site/docbook/reference/spring-batch-integration.xml index ca4e56e8b..c57d7de7a 100644 --- a/src/site/docbook/reference/spring-batch-integration.xml +++ b/src/site/docbook/reference/spring-batch-integration.xml @@ -77,8 +77,7 @@ namespace declarations to your Spring XML Application Context file: - -<beans xmlns="http://www.springframework.org/schema/beans" + <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:batch-int="http://www.springframework.org/schema/batch-integration" xsi:schemaLocation=" @@ -87,14 +86,12 @@ ... -</beans> - +</beans> A fully configured Spring XML Application Context file for Spring Batch Integration may look like the following: - -<beans xmlns="http://www.springframework.org/schema/beans" + <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration" xmlns:batch="http://www.springframework.org/schema/batch" @@ -111,8 +108,7 @@ ... -</beans> - +</beans> Appending version numbers to the referenced XSD file is also allowed but, as a version-less declaration will always use the @@ -194,15 +190,14 @@ Transforming a file into a JobLaunchRequest - -package io.spring.sbi; + package io.spring.sbi; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobParametersBuilder; @@ -234,8 +229,7 @@ public class FileMessageToJobRequest { return new JobLaunchRequest(job, jobParametersBuilder.toJobParameters()); } -} - +} The JobExecution Response @@ -281,8 +275,7 @@ public class FileMessageToJobRequest { Spring Batch Integration Configuration - -<int:channel id="inboundFileChannel"/> + <int:channel id="inboundFileChannel"/> <int:channel id="outboundJobRequestChannel"/> <int:channel id="jobLaunchReplyChannel"/> @@ -304,8 +297,7 @@ public class FileMessageToJobRequest { <batch-int:job-launching-gateway request-channel="outboundJobRequestChannel" reply-channel="jobLaunchReplyChannel"/> -<int:logging-channel-adapter channel="jobLaunchReplyChannel"/> - +<int:logging-channel-adapter channel="jobLaunchReplyChannel"/> Now that we are polling for files and launching jobs, we need to configure for example our Spring Batch @@ -315,13 +307,11 @@ public class FileMessageToJobRequest { Example ItemReader Configuration - -<bean id="itemReader" class="org.springframework.batch.item.file.FlatFileItemReader" + <bean id="itemReader" class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"> <property name="resource" value="file://#{jobParameters['input.file.name']}"/> ... -</bean> - +</bean> The main points of interest here are injecting the value of #{jobParameters['input.file.name']} @@ -439,12 +429,10 @@ public class FileMessageToJobRequest { a global default Poller or provide a Poller sub-element to the Job Launching Gateway: - -<batch-int:job-launching-gateway request-channel="queueChannel" + <batch-int:job-launching-gateway request-channel="queueChannel" reply-channel="replyChannel" job-launcher="jobLauncher"> <int:poller fixed-rate="1000"/> -</batch-int:job-launching-gateway> - +</batch-int:job-launching-gateway> @@ -519,7 +507,7 @@ public class FileMessageToJobRequest { @@ -532,20 +520,17 @@ public class FileMessageToJobRequest { First create the notifications integration beans: - -<int:channel id="stepExecutionsChannel"/> + <int:channel id="stepExecutionsChannel"/> <int:gateway id="notificationExecutionsListener" service-interface="org.springframework.batch.core.StepExecutionListener" default-request-channel="stepExecutionsChannel"/> -<int:logging-channel-adapter channel="stepExecutionsChannel"/> - +<int:logging-channel-adapter channel="stepExecutionsChannel"/> Then modify your job to add a step level listener: - -<job id="importPayments"> + <job id="importPayments"> <step id="step1"> <tasklet ../> <chunk ../> @@ -555,8 +540,7 @@ public class FileMessageToJobRequest { </tasklet> ... </step> -</job> - +</job> Asynchronous Processors @@ -581,8 +565,7 @@ public class FileMessageToJobRequest { and AsyncItemWriter are simple, first the AsyncItemProcessor: - -<bean id="processor" + <bean id="processor" class="org.springframework.batch.integration.async.AsyncItemProcessor"> <property name="delegate"> <bean class="your.ItemProcessor"/> @@ -590,8 +573,7 @@ public class FileMessageToJobRequest { <property name="taskExecutor"> <bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/> </property> -</bean> - +</bean> The property "delegate" is actually a reference to your ItemProcessor bean and @@ -601,14 +583,12 @@ public class FileMessageToJobRequest { Then we configure the AsyncItemWriter: - -<bean id="itemWriter" + <bean id="itemWriter" class="org.springframework.batch.integration.async.AsyncItemWriter"> <property name="delegate"> <bean id="itemWriter" class="your.ItemWriter"/> </property> -</bean> - +</bean> Again, the property "delegate" is actually a reference to your ItemWriter bean. @@ -647,7 +627,7 @@ public class FileMessageToJobRequest { @@ -674,16 +654,14 @@ public class FileMessageToJobRequest { A simple job with a step to be remotely chunked would have a configuration similar to the following: - -<job id="personJob"> + <job id="personJob"> <step id="step1"> <tasklet> <chunk reader="itemReader" writer="itemWriter" commit-interval="200"/> </tasklet> ... </step> -</job> - +</job> The ItemReader reference would point to the bean you would like to use for reading data on the master. The ItemWriter reference @@ -695,8 +673,7 @@ public class FileMessageToJobRequest { advised to check any additional component properties such as throttle limits and so on when implementing your use case. - -<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> + <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://localhost:61616"/> </bean> @@ -727,8 +704,7 @@ public class FileMessageToJobRequest { <int-jms:message-driven-channel-adapter id="jmsReplies" destination-name="replies" - channel="replies"/> - + channel="replies"/> This configuration provides us with a number of beans. We configure our messaging middleware using ActiveMQ and @@ -741,8 +717,7 @@ public class FileMessageToJobRequest { Now lets move on to the slave configuration: - -<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> + <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://localhost:61616"/> </bean> @@ -776,8 +751,7 @@ public class FileMessageToJobRequest { </property> </bean> </property> -</bean> - +</bean> Most of these configuration items should look familiar from the master configuration. Slaves do not need access to things like @@ -810,7 +784,7 @@ public class FileMessageToJobRequest { @@ -865,8 +839,7 @@ public class FileMessageToJobRequest { the MessageChannelPartitionHandler and JMS configuration: - -<bean id="partitionHandler" + <bean id="partitionHandler" class="org.springframework.batch.integration.partition.MessageChannelPartitionHandler"> <property name="stepName" value="step1"/> <property name="gridSize" value="3"/> @@ -912,20 +885,17 @@ public class FileMessageToJobRequest { </int:channel> <bean id="stepLocator" - class="org.springframework.batch.integration.partition.BeanFactoryStepLocator" /> - + class="org.springframework.batch.integration.partition.BeanFactoryStepLocator" /> Also ensure the partition handler attribute maps to the partitionHandler bean: - -<job id="personJob"> + <job id="personJob"> <step id="step1.master"> <partition partitioner="partitioner" handler="partitionHandler"/> ... </step> -</job> - +</job> diff --git a/src/site/docbook/reference/step.xml b/src/site/docbook/reference/step.xml index 6dd1cbc09..338af0310 100644 --- a/src/site/docbook/reference/step.xml +++ b/src/site/docbook/reference/step.xml @@ -54,7 +54,7 @@ Below is a code representation of the same concepts shown above: - List items = new Arraylist(); + List items = new Arraylist(); for(int i = 0; i < commitInterval; i++){ Object item = itemReader.read() Object processedItem = itemProcessor.process(item); @@ -70,7 +70,7 @@ itemWriter.write(items); potentially contain many collaborators. In order to ease configuration, the Spring Batch namespace can be used: - <job id="sampleJob" job-repository="jobRepository"> + <job id="sampleJob" job-repository="jobRepository"> <step id="step1"> <tasklet transaction-manager="transactionManager"> <chunk reader="itemReader" writer="itemWriter" commit-interval="10"/> @@ -138,7 +138,7 @@ itemWriter.write(items); allowStartIfComplete=true. Additionally, the commitInterval will be '5' since it is overridden by the "concreteStep1": - <step id="parentStep"> + <step id="parentStep"> <tasklet allow-start-if-complete="true"> <chunk reader="itemReader" writer="itemWriter" commit-interval="10"/> </tasklet> @@ -183,7 +183,7 @@ itemWriter.write(items); be abstract. The Step "concreteStep2" will have 'itemReader', 'itemWriter', and commitInterval=10. - <step id="abstractParentStep" abstract="true"> + <step id="abstractParentStep" abstract="true"> <tasklet> <chunk commit-interval="10"/> </tasklet> @@ -214,7 +214,7 @@ itemWriter.write(items); listenerOne and listenerTwo: - <step id="listenersParentStep" abstract="true"> + <step id="listenersParentStep" abstract="true"> <listeners> <listener ref="listenerOne"/> <listeners> @@ -246,7 +246,7 @@ itemWriter.write(items); number of items that are processed within a commit can be configured. - <job id="sampleJob"> + <job id="sampleJob"> <step id="step1"> <tasklet> <chunk reader="itemReader" writer="itemWriter" as a Step that can be run infinitely. Below is an example start limit configuration: - <step id="step1"> - <tasklet start-limit="1"> + <step id="step1"> + <tasklet start-limit="1"> <chunk reader="itemReader" writer="itemWriter" commit-interval="10"/> </tasklet> </step> @@ -309,8 +309,8 @@ itemWriter.write(items); successfully, will be skipped. Setting allow-start-if-complete to "true" overrides this so that the step will always run: - <step id="step1"> - <tasklet allow-start-if-complete="true"> + <step id="step1"> + <tasklet allow-start-if-complete="true"> <chunk reader="itemReader" writer="itemWriter" commit-interval="10"/> </tasklet> </step> @@ -319,7 +319,7 @@ itemWriter.write(items);
Step Restart Configuration Example - <job id="footballJob" restartable="true"> + <job id="footballJob" restartable="true"> <step id="playerload" next="gameLoad"> <tasklet> <chunk reader="playerFileItemReader" writer="playerWriter" @@ -451,7 +451,9 @@ itemWriter.write(items); loaded because it was formatted incorrectly or was missing necessary information, then there probably won't be issues. Usually these bad records are logged as well, which will be covered later when discussing - listeners.<step id="step1"> + listeners. + + <step id="step1"> <tasklet> <chunk reader="flatFileItemReader" writer="itemWriter" commit-interval="10" skip-limit="10"> @@ -460,7 +462,7 @@ itemWriter.write(items); </skippable-exception-classes> </chunk> </tasklet> -</step> +</step> In this example, a FlatFileItemReader is used, and if at any point a @@ -475,7 +477,9 @@ itemWriter.write(items); Job to fail. In certain scenarios this may be the correct behavior. However, in other scenarios it may be easier to identify which exceptions should cause failure and skip everything - else:<step id="step1"> + else: + + <step id="step1"> <tasklet> <chunk reader="flatFileItemReader" writer="itemWriter" commit-interval="10" skip-limit="10"> @@ -485,7 +489,7 @@ itemWriter.write(items); </skippable-exception-classes> </chunk> </tasklet> -</step> +</step> By 'including' java.lang.Exception as a skippable exception class, the configuration indicates that all @@ -517,7 +521,7 @@ itemWriter.write(items); process holds a lock on, waiting and trying again might result in success. In this case, retry should be configured: - <step id="step1"> + <step id="step1"> <tasklet> <chunk reader="itemReader" writer="itemWriter" commit-interval="2" retry-limit="3"> @@ -548,7 +552,7 @@ itemWriter.write(items); the Step can be configured with a list of exceptions that should not cause rollback. - <step id="step1"> + <step id="step1"> <tasklet> <chunk reader="itemReader" writer="itemWriter" commit-interval="2"/> <no-rollback-exception-classes> @@ -570,7 +574,7 @@ itemWriter.write(items); this reason, the step can be configured to not buffer the items: - <step id="step1"> + <step id="step1"> <tasklet> <chunk reader="itemReader" writer="itemWriter" commit-interval="2" is-reader-transactional-queue="true"/> @@ -587,7 +591,7 @@ itemWriter.write(items); transaction attributes can be found in the spring core documentation. - <step id="step1"> + <step id="step1"> <tasklet> <chunk reader="itemReader" writer="itemWriter" commit-interval="2"/> <transaction-attributes isolation="DEFAULT" @@ -618,7 +622,7 @@ itemWriter.write(items); can be registered on the Step through the 'streams' element, as illustrated below: - <step id="step1"> + <step id="step1"> <tasklet> <chunk reader="itemReader" writer="compositeWriter" commit-interval="2"> <streams> @@ -675,7 +679,7 @@ itemWriter.write(items); the most granular level that it applies (chunk in the example given). - <step id="step1"> + <step id="step1"> <tasklet> <chunk reader="reader" writer="writer" commit-interval="10"/> <listeners> @@ -714,7 +718,7 @@ itemWriter.write(items); for notification before a Step is started and after it has ends, whether it ended normally or failed: - public interface StepExecutionListener extends StepListener { + public interface StepExecutionListener extends StepListener { void beforeStep(StepExecution stepExecution); @@ -749,10 +753,9 @@ itemWriter.write(items); useful to perform logic before a chunk begins processing or after a chunk has completed successfully: - public interface ChunkListener extends StepListener { + public interface ChunkListener extends StepListener { void beforeChunk(); - void afterChunk(); } @@ -790,15 +793,15 @@ itemWriter.write(items); When discussing skip logic above, it was mentioned that it may be beneficial to log the skipped records, so that they can be deal with later. In the case of read errors, this can be done with an - ItemReaderListener:public interface ItemReadListener<T> extends StepListener { + ItemReaderListener: + + public interface ItemReadListener<T> extends StepListener { void beforeRead(); - void afterRead(T item); - void onReadError(Exception ex); -} +} The beforeRead method will be called before each call to read on the @@ -833,12 +836,10 @@ itemWriter.write(items); Just as with the ItemReadListener, the processing of an item can be 'listened' to: - public interface ItemProcessListener<T, S> extends StepListener { + public interface ItemProcessListener<T, S> extends StepListener { void beforeProcess(T item); - void afterProcess(T item, S result); - void onProcessError(T item, Exception e); } @@ -876,12 +877,10 @@ itemWriter.write(items); The writing of an item can be 'listened' to with the ItemWriteListener: - public interface ItemWriteListener<S> extends StepListener { + public interface ItemWriteListener<S> extends StepListener { void beforeWrite(List<? extends S> items); - void afterWrite(List<? extends S> items); - void onWriteError(Exception exception, List<? extends S> items); } @@ -924,13 +923,10 @@ itemWriter.write(items); this reason, there is a separate interface for tracking skipped items: - - public interface SkipListener<T,S> extends StepListener { + public interface SkipListener<T,S> extends StepListener { void onSkipInRead(Throwable t); - void onSkipInProcess(T item, Throwable t); - void onSkipInWrite(S item, Throwable t); } @@ -1011,8 +1007,8 @@ itemWriter.write(items); Tasklet object; no <chunk/> element should be used within the <tasklet/>: - <step id="step1"> - <tasklet ref="myTasklet"/> + <step id="step1"> + <tasklet ref="myTasklet"/> </step> @@ -1034,7 +1030,7 @@ itemWriter.write(items); this class without having to write an adapter for the Tasklet interface: - <bean id="myTasklet" class="org.springframework.batch.core.step.tasklet.MethodInvokingTaskletAdapter"> + <bean id="myTasklet" class="o.s.b.core.step.tasklet.MethodInvokingTaskletAdapter"> <property name="targetObject"> <bean class="org.mycompany.FooDao"/> </property> @@ -1054,7 +1050,7 @@ itemWriter.write(items); project, is a Tasklet implementation with just such a responsibility: - public class FileDeletingTasklet implements Tasklet, InitializingBean { + public class FileDeletingTasklet implements Tasklet, InitializingBean { private Resource directory; @@ -1089,7 +1085,7 @@ itemWriter.write(items); that is left is to reference the Tasklet from the Step: - <job id="taskletJob"> + <job id="taskletJob"> <step id="deleteFilesInDir"> <tasklet ref="fileDeletingTasklet"/> </step> @@ -1140,11 +1136,13 @@ itemWriter.write(items); This can be achieved using the 'next' attribute of the step element: - <job id="job"> + <job id="job"> <step id="stepA" parent="s1" next="stepB" /> <step id="stepB" parent="s2" next="stepC"/> <step id="stepC" parent="s3" /> -</job>In the scenario above, 'step A' will execute +</job> + + In the scenario above, 'step A' will execute first because it is the first Step listed. If 'step A' completes normally, then 'step B' will execute, and so on. However, if 'step A' fails, then the entire Job @@ -1206,14 +1204,14 @@ itemWriter.write(items); The next element specifies a pattern to match and the step to execute next: - <job id="job"> + <job id="job"> <step id="stepA" parent="s1"> <next on="*" to="stepB" /> <next on="FAILED" to="stepC" /> </step> <step id="stepB" parent="s2" next="stepC" /> <step id="stepC" parent="s3" /> -</job> +</job> The "on" attribute of a transition element uses a simple pattern-matching scheme to match the ExitStatus @@ -1261,7 +1259,7 @@ itemWriter.write(items); it fails, and so on. The example above contains the following 'next' element: - <next on="FAILED" to="stepB" /> + <next on="FAILED" to="stepB" /> At first glance, it would appear that the 'on' attribute references the BatchStatus of the @@ -1278,7 +1276,7 @@ itemWriter.write(items); code needs to be different? A good example comes from the skip sample job within the samples project: - <step id="step1" parent="s1"> + <step id="step1" parent="s1"> <end on="FAILED" /> <next on="COMPLETED WITH SKIPS" to="errorPrint1" /> <next on="*" to="step2" /> @@ -1308,8 +1306,7 @@ itemWriter.write(items); change the exit code based on the condition of the execution having skipped records: - public class SkipCheckingListener extends StepExecutionListenerSupport { - + public class SkipCheckingListener extends StepExecutionListenerSupport { public ExitStatus afterStep(StepExecution stepExecution) { String exitCode = stepExecution.getExitStatus().getExitCode(); if (!exitCode.equals(ExitStatus.FAILED.getExitCode()) && @@ -1320,7 +1317,6 @@ itemWriter.write(items); return null; } } - } The above code is a StepExecutionListener @@ -1349,7 +1345,7 @@ itemWriter.write(items); after the following step executes, the Job will end: - <step id="stepC" parent="s3"/> + <step id="stepC" parent="s3"/> If no transitions are defined for a Step, then the Job's statuses will be defined as @@ -1408,7 +1404,7 @@ itemWriter.write(items); fails, the Job will not be restartable (because the status is COMPLETED). - <step id="step1" parent="s1" next="step2"> + <step id="step1" parent="s1" next="step2"> <step id="step2" parent="s2"> <end on="FAILED"/> @@ -1439,7 +1435,7 @@ itemWriter.write(items); Additionally, if step2 fails, and the Job is restarted, then execution will begin again on step2. - <step id="step1" parent="s1" next="step2"> + <step id="step1" parent="s1" next="step2"> <step id="step2" parent="s2"> <fail on="FAILED" exit-code="EARLY TERMINATION"/> @@ -1464,11 +1460,11 @@ itemWriter.write(items); the job will then stop. Once it is restarted, execution will begin on step2. - <step id="step1" parent="s1"> + <step id="step1" parent="s1"> <stop on="COMPLETED" restart="step2"/> </step> -<step id="step2" parent="s2"/> +<step id="step2" parent="s2"/>
@@ -1481,7 +1477,7 @@ itemWriter.write(items);
JobExecutionDecider can be used to assist in the decision. - public class MyDecider implements JobExecutionDecider { + public class MyDecider implements JobExecutionDecider { public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) { if (someCondition) { return "FAILED"; @@ -1490,12 +1486,12 @@ itemWriter.write(items); return "COMPLETED"; } } -} +}
In the job configuration, a "decision" tag will specify the decider to use as well as all of the transitions. - <job id="job"> + <job id="job"> <step id="step1" parent="s1" next="decision" /> <decision id="decision" decider="decider"> @@ -1507,7 +1503,7 @@ itemWriter.write(items); <step id="step3" parent="s3" /> </job> -<beans:bean id="decider" class="com.MyDecider"/> +<beans:bean id="decider" class="com.MyDecider"/>
@@ -1524,7 +1520,7 @@ itemWriter.write(items); elements such as the 'next' attribute or the 'next', 'end', 'fail', or 'pause' elements. - <split id="split1" next="step4"> + <split id="split1" next="step4"> <flow> <step id="step1" parent="s1" next="step2"/> <step id="step2" parent="s2"/> @@ -1545,7 +1541,7 @@ itemWriter.write(items); first is to simply declare the flow as a reference to one defined elsewhere: - <job id="job"> + <job id="job"> <flow id="job1.flow1" parent="flow1" next="step3"/> <step id="step3" parent="s3"/> </job> @@ -1568,7 +1564,7 @@ itemWriter.write(items); launches a separate job execution for the steps in the flow specified. Here is an example: - <job id="jobStepJob" restartable="true"> + <job id="jobStepJob" restartable="true"> <step id="jobStepJob.step1"> <job ref="job" job-launcher="jobLauncher" job-parameters-extractor="jobParametersExtractor"/> @@ -1579,7 +1575,7 @@ itemWriter.write(items); <bean id="jobParametersExtractor" class="org.spr...DefaultJobParametersExtractor"> <property name="keys" value="input.file"/> -</bean> +</bean> The job parameters extractor is a strategy that determines how a the ExecutionContext for the @@ -1604,7 +1600,7 @@ itemWriter.write(items); Flat File resources can be configured using standard Spring constructs: - <bean id="flatFileItemReader" + <bean id="flatFileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader"> <property name="resource" value="file://outputs/20070122.testStream.CustomerReportStep.TEMP.txt" /> @@ -1618,7 +1614,7 @@ itemWriter.write(items); at runtime as a parameter to the job. This could be solved using '-D' parameters, i.e. a system property: - <bean id="flatFileItemReader" + <bean id="flatFileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader"> <property name="resource" value="${input.file.name}" /> </bean> @@ -1637,7 +1633,7 @@ itemWriter.write(items); accomplish this, Spring Batch allows for the late binding of various Job and Step attributes: - <bean id="flatFileItemReader" scope="step" + <bean id="flatFileItemReader" scope="step" class="org.springframework.batch.item.file.FlatFileItemReader"> <property name="resource" value="#{jobParameters['input.file.name']}" /> </bean> @@ -1647,12 +1643,12 @@ itemWriter.write(items); ExecutionContext can be accessed in the same way: - <bean id="flatFileItemReader" scope="step" + <bean id="flatFileItemReader" scope="step" class="org.springframework.batch.item.file.FlatFileItemReader"> <property name="resource" value="#{jobExecutionContext['input.file.name']}" /> </bean> - <bean id="flatFileItemReader" scope="step" + <bean id="flatFileItemReader" scope="step" class="org.springframework.batch.item.file.FlatFileItemReader"> <property name="resource" value="#{stepExecutionContext['input.file.name']}" /> </bean> @@ -1680,7 +1676,7 @@ itemWriter.write(items); All of the late binding examples from above have a scope of "step" declared on the bean definition: - <bean id="flatFileItemReader" scope="step" + <bean id="flatFileItemReader" scope="step" class="org.springframework.batch.item.file.FlatFileItemReader"> <property name="resource" value="#{jobParameters[input.file.name]}" /> </bean> @@ -1692,7 +1688,7 @@ itemWriter.write(items); scope must be added explicitly, either by using the batch namespace: - <beans xmlns="http://www.springframework.org/schema/beans" + <beans xmlns="http://www.springframework.org/schema/beans" xmlns:batch="http://www.springframework.org/schema/batch" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="..."> @@ -1703,7 +1699,7 @@ itemWriter.write(items); or by including a bean definition explicitly for the StepScope (but not both): - <bean class="org.springframework.batch.core.scope.StepScope" /> + <bean class="org.springframework.batch.core.scope.StepScope" />
@@ -1715,20 +1711,20 @@ itemWriter.write(items); for late binding of references accessible from the JobContext using #{..} placeholders. Using this feature, bean properties can be pulled from the job or job execution context and the job parameters. E.g. - - <bean id="..." class="..." scope="job"> + + <bean id="..." class="..." scope="job"> <property name="name" value="#{jobParameters[input]}" /> </bean> - <bean id="..." class="..." scope="job"> + <bean id="..." class="..." scope="job"> <property name="name" value="#{jobExecutionContext['input.name']}.txt" /> </bean> - + Because it is not part of the Spring container by default, the scope must be added explicitly, either by using the batch namespace: - <beans xmlns="http://www.springframework.org/schema/beans" + <beans xmlns="http://www.springframework.org/schema/beans" xmlns:batch="http://www.springframework.org/schema/batch" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="..."> @@ -1739,7 +1735,7 @@ itemWriter.write(items); Or by including a bean definition explicitly for the JobScope (but not both): - <bean class="org.springframework.batch.core.scope.JobScope" /> + <bean class="org.springframework.batch.core.scope.JobScope" />
diff --git a/src/site/docbook/reference/testing.xml b/src/site/docbook/reference/testing.xml index d13c5d7d3..de1a0d98d 100644 --- a/src/site/docbook/reference/testing.xml +++ b/src/site/docbook/reference/testing.xml @@ -31,8 +31,8 @@ - @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(locations = { "/simple-job-launcher-context.xml", + @RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "/simple-job-launcher-context.xml", "/jobs/skipSampleJob.xml" }) public class SkipSampleFunctionalTests extends AbstractJobTests { ... } @@ -59,8 +59,8 @@ public class SkipSampleFunctionalTests extends AbstractJobTests { ... }Job ended with status "COMPLETED". - @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(locations = { "/simple-job-launcher-context.xml", + @RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "/simple-job-launcher-context.xml", "/jobs/skipSampleJob.xml" }) public class SkipSampleFunctionalTests { @@ -78,7 +78,7 @@ public class SkipSampleFunctionalTests { public void testJob() throws Exception { simpleJdbcTemplate.update("delete from CUSTOMER"); for (int i = 1; i <= 10; i++) { - simpleJdbcTemplate.update("insert into CUSTOMER values (?, 0, ?, 100000)", + simpleJdbcTemplate.update("insert into CUSTOMER values (?, 0, ?, 100000)", i, "customer" + i); } @@ -102,7 +102,7 @@ public class SkipSampleFunctionalTests { targeted tests by allowing the test to set up data for just that step and to validate its results directly. - JobExecution jobExecution = jobLauncherTestUtils.launchStep("loadFileStep"); + JobExecution jobExecution = jobLauncherTestUtils.launchStep("loadFileStep");
@@ -119,8 +119,8 @@ public class SkipSampleFunctionalTests { The listener is declared at the class level, and its job is to create a step execution context for each test method. For example: - @ContextConfiguration -@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class, + @ContextConfiguration +@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class, StepScopeTestExecutionListener.class }) @RunWith(SpringJUnit4ClassRunner.class) public class StepScopeTestExecutionListenerIntegrationTests { @@ -141,7 +141,7 @@ public class StepScopeTestExecutionListenerIntegrationTests { // The reader is initialized and bound to the input data assertNotNull(reader.read()); } - + } There are two TestExecutionListeners, one @@ -162,9 +162,8 @@ public class StepScopeTestExecutionListenerIntegrationTests { StepScopeTestUtils. For example, to count the number of items available in the reader above: - int count = StepScopeTestUtils.doInStepScope(stepExecution, + int count = StepScopeTestUtils.doInStepScope(stepExecution, new Callable<Integer>() { - public Integer call() throws Exception { int count = 0; @@ -172,9 +171,7 @@ public class StepScopeTestExecutionListenerIntegrationTests { while (reader.read() != null) { count++; } - return count; - } });
@@ -194,10 +191,10 @@ public class StepScopeTestExecutionListenerIntegrationTests { file with the expected output and to compare it to the actual result: - private static final String EXPECTED_FILE = "src/main/resources/data/input.txt"; + private static final String EXPECTED_FILE = "src/main/resources/data/input.txt"; private static final String OUTPUT_FILE = "target/test-outputs/output.txt"; -AssertFile.assertFileEquals(new FileSystemResource(EXPECTED_FILE), +AssertFile.assertFileEquals(new FileSystemResource(EXPECTED_FILE), new FileSystemResource(OUTPUT_FILE)); @@ -209,9 +206,9 @@ AssertFile.assertFileEquals(new FileSystemResource(EXPECTED_FILE), example is a StepExecutionListener, as illustrated below: - public class NoWorkFoundStepExecutionListener extends StepExecutionListenerSupport { + public class NoWorkFoundStepExecutionListener extends StepExecutionListenerSupport { - public ExitStatus afterStep(StepExecution stepExecution) { + public ExitStatus afterStep(StepExecution stepExecution) { if (stepExecution.getReadCount() == 0) { throw new NoWorkFoundException("Step has not processed any items"); } @@ -226,12 +223,12 @@ AssertFile.assertFileEquals(new FileSystemResource(EXPECTED_FILE), attempting to unit test classes that implement interfaces requiring Spring Batch domain objects. Consider the above listener's unit test: - private NoWorkFoundStepExecutionListener tested = new NoWorkFoundStepExecutionListener(); + private NoWorkFoundStepExecutionListener tested = new NoWorkFoundStepExecutionListener(); @Test public void testAfterStep() { StepExecution stepExecution = new StepExecution("NoProcessingStep", - new JobExecution(new JobInstance(1L, new JobParameters(), + new JobExecution(new JobInstance(1L, new JobParameters(), "NoProcessingJob"))); stepExecution.setReadCount(0); @@ -256,7 +253,7 @@ public void testAfterStep() { Given this factory, the unit test can be updated to be more concise: - private NoWorkFoundStepExecutionListener tested = new NoWorkFoundStepExecutionListener(); + private NoWorkFoundStepExecutionListener tested = new NoWorkFoundStepExecutionListener(); @Test public void testAfterStep() {