BATCH-1396: added step scope to retry and fixed bugs

This commit is contained in:
Dave Syer
2011-03-21 09:35:20 +00:00
parent aeadf8b02e
commit 58ad6bd542
12 changed files with 239 additions and 57 deletions

View File

@@ -9,10 +9,13 @@
<step id="playerload" next="gameLoad">
<tasklet>
<chunk reader="playerFileItemReader" writer="playerWriter" commit-interval="${job.commit.interval}"
skip-limit="#{jobParameters['skip.limit']}">
skip-limit="#{jobParameters['skip.limit']}" retry-limit="#{jobParameters['retry.limit']}">
<skippable-exception-classes>
<include class="org.springframework.dao.DataAccessException" />
</skippable-exception-classes>
<retryable-exception-classes>
<include class="org.springframework.dao.DataAccessException" />
</retryable-exception-classes>
</chunk>
</tasklet>
</step>

View File

@@ -89,7 +89,7 @@ public class FootballJobSkipIntegrationTests {
logger.info("Processed: " + stepExecution);
}
// They all skip on the second execution because of a primary key violation
execution = jobLauncher.run(job, new JobParametersBuilder().addLong("skip.limit", 100000L)
execution = jobLauncher.run(job, new JobParametersBuilder().addLong("skip.limit", 100000L).addLong("retry.limit", 2L)
.toJobParameters());
assertEquals(BatchStatus.COMPLETED, execution.getStatus());
for (StepExecution stepExecution : execution.getStepExecutions()) {

View File

@@ -18,8 +18,10 @@ package org.springframework.batch.core.configuration.xml;
import java.util.List;
import org.springframework.batch.core.listener.StepListenerMetaData;
import org.springframework.batch.core.step.item.ForceRollbackForWriteSkipException;
import org.springframework.batch.core.step.skip.LimitCheckingItemSkipPolicy;
import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
import org.springframework.batch.retry.policy.SimpleRetryPolicy;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
@@ -119,15 +121,19 @@ public class ChunkElementParser {
}
String skipLimit = element.getAttribute("skip-limit");
ManagedMap skippableExceptions = handleExceptionElement(element, parserContext, "skippable-exception-classes");
boolean hasSkipPolicy = false;
if (StringUtils.hasText(skipLimit)) {
if (skippableExceptions == null) {
parserContext.getReaderContext().error(
"The <chunk/> element must have skippable-exceptions if a skip-limit is specified.", element);
}
if (skipLimit.startsWith("#")) {
// It's a late binding expression, so we need step scope...
BeanDefinitionBuilder skipPolicy = BeanDefinitionBuilder
.genericBeanDefinition(LimitCheckingItemSkipPolicy.class);
skipPolicy.setScope("step");
handleExceptionElement(element, parserContext, skipPolicy.getBeanDefinition().getPropertyValues(),
"skippable-exception-classes", "skippableExceptionMap");
skipPolicy.addPropertyValue("skippableExceptionMap", skippableExceptions);
skipPolicy.addPropertyValue("skipLimit", skipLimit);
propertyValues.addPropertyValue("skipPolicy", skipPolicy.getBeanDefinition());
hasSkipPolicy = true;
@@ -137,21 +143,45 @@ public class ChunkElementParser {
}
}
if (!hasSkipPolicy) {
handleExceptionElement(element, parserContext, propertyValues, "skippable-exception-classes",
"skippableExceptionClasses");
// Even if there is no retryLimit, we can still accept exception
// classes for an abstract parent bean definition
propertyValues.addPropertyValue("skippableExceptionClasses", skippableExceptions);
}
handleItemHandler("skip-policy", "skipPolicy", null, false, element, parserContext, propertyValues,
underspecified);
String retryLimit = element.getAttribute("retry-limit");
ManagedMap retryableExceptions = handleExceptionElement(element, parserContext, "retryable-exception-classes");
boolean hasRetryPolicy = false;
if (StringUtils.hasText(retryLimit)) {
if (retryableExceptions == null) {
parserContext.getReaderContext().error(
"The <chunk/> element must have retryable-exceptions if a retry-limit is specified.", element);
}
if (retryLimit.startsWith("#")) {
// It's a late binding expression, so we need step scope...
BeanDefinitionBuilder retryPolicy = BeanDefinitionBuilder
.genericBeanDefinition(SimpleRetryPolicy.class);
retryPolicy.setScope("step");
retryPolicy.addPropertyValue("maxAttempts", retryLimit);
retryPolicy.addPropertyValue("retryableExceptions", retryableExceptions);
propertyValues.addPropertyValue("retryPolicy", retryPolicy.getBeanDefinition());
hasRetryPolicy = true;
}
else {
propertyValues.addPropertyValue("retryLimit", retryLimit);
}
}
if (!hasRetryPolicy) {
// Even if there is no retryLimit, we can still accept exception
// classes for an abstract parent bean definition
propertyValues.addPropertyValue("retryableExceptionClasses", retryableExceptions);
}
handleItemHandler("retry-policy", "retryPolicy", null, false, element, parserContext, propertyValues,
underspecified);
String retryLimit = element.getAttribute("retry-limit");
if (StringUtils.hasText(retryLimit)) {
propertyValues.addPropertyValue("retryLimit", retryLimit);
}
String cacheCapacity = element.getAttribute("cache-capacity");
if (StringUtils.hasText(cacheCapacity)) {
propertyValues.addPropertyValue("cacheCapacity", cacheCapacity);
@@ -167,9 +197,6 @@ public class ChunkElementParser {
propertyValues.addPropertyValue("processorTransactional", isProcessorTransactional);
}
handleExceptionElement(element, parserContext, propertyValues, "retryable-exception-classes",
"retryableExceptionClasses");
handleRetryListenersElement(element, propertyValues, parserContext, bd);
handleStreamsElement(element, propertyValues, parserContext);
@@ -315,23 +342,24 @@ public class ChunkElementParser {
}
@SuppressWarnings("unchecked")
private void handleExceptionElement(Element element, ParserContext parserContext,
MutablePropertyValues propertyValues, String exceptionListName, String propertyName) {
private ManagedMap handleExceptionElement(Element element, ParserContext parserContext, String exceptionListName) {
List<Element> children = DomUtils.getChildElementsByTagName(element, exceptionListName);
if (children.size() == 1) {
Element exceptionClassesElement = children.get(0);
ManagedMap map = new ManagedMap();
Element exceptionClassesElement = children.get(0);
map.setMergeEnabled(exceptionClassesElement.hasAttribute(MERGE_ATTR)
&& Boolean.valueOf(exceptionClassesElement.getAttribute(MERGE_ATTR)));
addExceptionClasses("include", true, exceptionClassesElement, map, parserContext);
addExceptionClasses("exclude", false, exceptionClassesElement, map, parserContext);
propertyValues.addPropertyValue(propertyName, map);
map.put(ForceRollbackForWriteSkipException.class, true);
return map;
}
else if (children.size() > 1) {
parserContext.getReaderContext().error(
"The <" + exceptionListName + "/> element may not appear more than once in a single <"
+ element.getNodeName() + "/>.", element);
}
return null;
}
@SuppressWarnings("unchecked")

View File

@@ -29,6 +29,7 @@ import org.springframework.batch.classify.SubclassClassifier;
import org.springframework.batch.core.JobInterruptedException;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.step.FatalStepExecutionException;
import org.springframework.batch.core.step.skip.CompositeSkipPolicy;
import org.springframework.batch.core.step.skip.ExceptionClassifierSkipPolicy;
import org.springframework.batch.core.step.skip.LimitCheckingItemSkipPolicy;
import org.springframework.batch.core.step.skip.NeverSkipItemSkipPolicy;
@@ -47,6 +48,7 @@ import org.springframework.batch.retry.RetryException;
import org.springframework.batch.retry.RetryListener;
import org.springframework.batch.retry.RetryPolicy;
import org.springframework.batch.retry.backoff.BackOffPolicy;
import org.springframework.batch.retry.policy.CompositeRetryPolicy;
import org.springframework.batch.retry.policy.ExceptionClassifierRetryPolicy;
import org.springframework.batch.retry.policy.MapRetryContextCache;
import org.springframework.batch.retry.policy.NeverRetryPolicy;
@@ -373,21 +375,12 @@ public class FaultTolerantStepFactoryBean<T, S> extends SimpleStepFactoryBean<T,
@Override
protected SimpleChunkProvider<T> configureChunkProvider() {
if (skipPolicy != null) {
if (!skippableExceptionClasses.isEmpty()) {
logger.info("Skippable exceptions will be ignored because a SkipPolicy was specified explicitly");
}
if (skipLimit > 0) {
logger.info("Skip limit will be ignored because a SkipPolicy was specified explicitly");
}
}
SkipPolicy readSkipPolicy = skipPolicy != null ? skipPolicy : new LimitCheckingItemSkipPolicy(skipLimit,
getSkippableExceptionClasses());
SkipPolicy readSkipPolicy = createSkipPolicy();
readSkipPolicy = getFatalExceptionAwareProxy(readSkipPolicy);
FaultTolerantChunkProvider<T> chunkProvider = new FaultTolerantChunkProvider<T>(getItemReader(),
getChunkOperations());
chunkProvider.setMaxSkipsOnRead(Math.max(getCommitInterval(), FaultTolerantChunkProvider.DEFAULT_MAX_SKIPS_ON_READ));
chunkProvider.setMaxSkipsOnRead(Math.max(getCommitInterval(),
FaultTolerantChunkProvider.DEFAULT_MAX_SKIPS_ON_READ));
chunkProvider.setSkipPolicy(readSkipPolicy);
chunkProvider.setRollbackClassifier(getRollbackClassifier());
@@ -395,6 +388,22 @@ public class FaultTolerantStepFactoryBean<T, S> extends SimpleStepFactoryBean<T,
}
/**
* @return
*/
protected SkipPolicy createSkipPolicy() {
SkipPolicy skipPolicy = this.skipPolicy;
LimitCheckingItemSkipPolicy limitCheckingItemSkipPolicy = new LimitCheckingItemSkipPolicy(skipLimit,
getSkippableExceptionClasses());
if (skipPolicy == null) {
skipPolicy = limitCheckingItemSkipPolicy;
}
else if (limitCheckingItemSkipPolicy != null) {
skipPolicy = new CompositeSkipPolicy(new SkipPolicy[] { skipPolicy, limitCheckingItemSkipPolicy });
}
return skipPolicy;
}
/**
* @return {@link ChunkProcessor} configured for fault-tolerance.
*/
@@ -408,8 +417,7 @@ public class FaultTolerantStepFactoryBean<T, S> extends SimpleStepFactoryBean<T,
chunkProcessor.setBuffering(!isReaderTransactionalQueue());
chunkProcessor.setProcessorTransactional(processorTransactional);
SkipPolicy writeSkipPolicy = skipPolicy != null ? skipPolicy : new LimitCheckingItemSkipPolicy(skipLimit,
getSkippableExceptionClasses());
SkipPolicy writeSkipPolicy = createSkipPolicy();
writeSkipPolicy = getFatalExceptionAwareProxy(writeSkipPolicy);
chunkProcessor.setWriteSkipPolicy(writeSkipPolicy);
chunkProcessor.setProcessSkipPolicy(writeSkipPolicy);
@@ -436,13 +444,21 @@ public class FaultTolerantStepFactoryBean<T, S> extends SimpleStepFactoryBean<T,
*/
private BatchRetryTemplate configureRetry() {
RetryPolicy retryPolicy = this.retryPolicy;
SimpleRetryPolicy simpleRetryPolicy = null;
Map<Class<? extends Throwable>, Boolean> map = new HashMap<Class<? extends Throwable>, Boolean>(
retryableExceptionClasses);
map.put(ForceRollbackForWriteSkipException.class, true);
simpleRetryPolicy = new SimpleRetryPolicy(retryLimit, map);
if (retryPolicy == null) {
Map<Class<? extends Throwable>, Boolean> map = new HashMap<Class<? extends Throwable>, Boolean>(
retryableExceptionClasses);
map.put(ForceRollbackForWriteSkipException.class, true);
// set.addAll(noRollbackExceptionClasses); // should only be
// retryable on write
retryPolicy = new SimpleRetryPolicy(retryLimit, map);
retryPolicy = simpleRetryPolicy;
}
else if ((!retryableExceptionClasses.isEmpty() && retryLimit > 0)) {
CompositeRetryPolicy compositeRetryPolicy = new CompositeRetryPolicy();
compositeRetryPolicy.setPolicies(new RetryPolicy[] { retryPolicy, simpleRetryPolicy });
retryPolicy = compositeRetryPolicy;
}
RetryPolicy retryPolicyWrapper = getFatalExceptionAwareProxy(retryPolicy);
@@ -544,7 +560,7 @@ public class FaultTolerantStepFactoryBean<T, S> extends SimpleStepFactoryBean<T,
exceptions.add(fatal);
}
}
nonRetryableExceptionClasses = (List<Class<? extends Throwable>>)exceptions;
nonRetryableExceptionClasses = (List<Class<? extends Throwable>>) exceptions;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2006-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.step.skip;
/**
* @author Dave Syer
*
*/
public class CompositeSkipPolicy implements SkipPolicy {
private SkipPolicy[] skipPolicies;
public CompositeSkipPolicy() {
this(new SkipPolicy[0]);
}
public CompositeSkipPolicy(SkipPolicy[] skipPolicies) {
this.skipPolicies = skipPolicies;
}
public void setSkipPolicies(SkipPolicy[] skipPolicies) {
this.skipPolicies = skipPolicies;
}
public boolean shouldSkip(Throwable t, int skipCount) throws SkipLimitExceededException {
for (SkipPolicy policy : skipPolicies) {
if (policy.shouldSkip(t, skipCount)) {
return true;
}
}
return false;
}
}

View File

@@ -29,6 +29,7 @@ import org.junit.Test;
import org.springframework.batch.classify.SubclassClassifier;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.step.item.SimpleChunkProcessor;
import org.springframework.batch.core.step.skip.SkipPolicy;
import org.springframework.batch.core.step.tasklet.TaskletStep;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.support.CompositeItemStream;
@@ -37,11 +38,13 @@ import org.springframework.batch.retry.listener.RetryListenerSupport;
import org.springframework.batch.retry.policy.SimpleRetryPolicy;
import org.springframework.beans.PropertyAccessorUtils;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.dao.CannotAcquireLockException;
import org.springframework.dao.CannotSerializeTransactionException;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.dao.DeadlockLoserDataAccessException;
import org.springframework.dao.PessimisticLockingFailureException;
import org.springframework.test.util.ReflectionTestUtils;
@@ -68,11 +71,32 @@ public class ChunkElementParserTests {
@Test
public void testCommitIntervalLateBinding() throws Exception {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(
"org/springframework/batch/core/configuration/xml/ChunkElementCommitIntervalParserTests-context.xml");
"org/springframework/batch/core/configuration/xml/ChunkElementLateBindingParserTests-context.xml");
Step step = (Step) context.getBean("s1", Step.class);
assertNotNull("Step not parsed", step);
}
@Test
public void testSkipAndRetryAttributes() throws Exception {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(
"org/springframework/batch/core/configuration/xml/ChunkElementSkipAndRetryAttributeParserTests-context.xml");
Step step = (Step) context.getBean("s1", Step.class);
assertNotNull("Step not parsed", step);
}
@Test
public void testIllegalSkipAndRetryAttributes() throws Exception {
try {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(
"org/springframework/batch/core/configuration/xml/ChunkElementIllegalSkipAndRetryAttributeParserTests-context.xml");
Step step = (Step) context.getBean("s1", Step.class);
assertNotNull("Step not parsed", step);
fail("Expected BeanDefinitionParsingException");
} catch (BeanDefinitionParsingException e) {
// expected
}
}
@Test
public void testRetryPolicyAttribute() throws Exception {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(
@@ -98,19 +122,18 @@ public class ChunkElementParserTests {
public void testSkipPolicyAttribute() throws Exception {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(
"org/springframework/batch/core/configuration/xml/ChunkElementSkipPolicyParserTests-context.xml");
Map<Class<? extends Throwable>, Boolean> skippable = getSkippableExceptionClasses("s1", context);
assertEquals(2, skippable.size());
containsClassified(skippable, NullPointerException.class, true);
containsClassified(skippable, ArithmeticException.class, true);
SkipPolicy policy = getSkipPolicy("s1", context);
assertTrue(policy.shouldSkip(new NullPointerException(), 0));
assertTrue(policy.shouldSkip(new ArithmeticException(), 0));
}
@Test
public void testSkipPolicyElement() throws Exception {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(
"org/springframework/batch/core/configuration/xml/ChunkElementSkipPolicyParserTests-context.xml");
Map<Class<? extends Throwable>, Boolean> skippable = getSkippableExceptionClasses("s2", context);
assertEquals(1, skippable.size());
containsClassified(skippable, ArithmeticException.class, true);
SkipPolicy policy = getSkipPolicy("s2", context);
assertFalse(policy.shouldSkip(new NullPointerException(), 0));
assertTrue(policy.shouldSkip(new ArithmeticException(), 0));
}
@Test
@@ -164,6 +187,7 @@ public class ChunkElementParserTests {
@Test
public void testInheritSkippable() throws Exception {
Map<Class<? extends Throwable>, Boolean> skippable = getSkippableExceptionClasses("s1", getContext());
System.err.println(skippable);
assertEquals(5, skippable.size());
containsClassified(skippable, NullPointerException.class, true);
containsClassified(skippable, ArithmeticException.class, true);
@@ -175,9 +199,9 @@ public class ChunkElementParserTests {
public void testInheritSkippableWithNoMerge() throws Exception {
Map<Class<? extends Throwable>, Boolean> skippable = getSkippableExceptionClasses("s2", getContext());
assertEquals(3, skippable.size());
containsClassified(skippable, NullPointerException.class, true);
containsClassified(skippable, IllegalArgumentException.class, true);
assertFalse(skippable.containsKey(ArithmeticException.class));
containsClassified(skippable, CannotAcquireLockException.class, false);
containsClassified(skippable, ConcurrencyFailureException.class, false);
assertFalse(skippable.containsKey(DeadlockLoserDataAccessException.class));
}
@@ -244,6 +268,11 @@ public class ChunkElementParserTests {
"skippableExceptionClassifier");
}
private SkipPolicy getSkipPolicy(String stepName,
ApplicationContext ctx) throws Exception {
return (SkipPolicy) getNestedPathInStep(stepName, ctx, "tasklet.chunkProvider.skipPolicy");
}
private Map<Class<? extends Throwable>, Boolean> getRetryableExceptionClasses(String stepName,
ApplicationContext ctx) throws Exception {
return getNestedExceptionMap(stepName, ctx,

View File

@@ -40,6 +40,7 @@ import org.springframework.batch.core.repository.support.SimpleJobRepository;
import org.springframework.batch.core.step.AbstractStep;
import org.springframework.batch.core.step.item.FatalSkippableException;
import org.springframework.batch.core.step.item.FatalRuntimeException;
import org.springframework.batch.core.step.item.ForceRollbackForWriteSkipException;
import org.springframework.batch.core.step.item.SkippableException;
import org.springframework.batch.core.step.item.SkippableRuntimeException;
import org.springframework.batch.core.step.tasklet.Tasklet;
@@ -428,9 +429,11 @@ public class StepParserTests {
skippable.put(SkippableException.class, true);
skippable.put(FatalRuntimeException.class, false);
skippable.put(FatalSkippableException.class, false);
skippable.put(ForceRollbackForWriteSkipException.class, true);
Map<Class<? extends Throwable>, Boolean> retryable = new HashMap<Class<? extends Throwable>, Boolean>();
retryable.put(DeadlockLoserDataAccessException.class, true);
retryable.put(FatalSkippableException.class, true);
retryable.put(ForceRollbackForWriteSkipException.class, true);
List<Class<? extends ItemStream>> streams = Arrays.asList(CompositeItemStream.class, TestReader.class);
List<Class<? extends RetryListener>> retryListeners = Arrays.asList(RetryListenerSupport.class,
DummyRetryListener.class);
@@ -464,8 +467,10 @@ public class StepParserTests {
Map<Class<? extends Throwable>, Boolean> skippable = new HashMap<Class<? extends Throwable>, Boolean>();
skippable.put(SkippableException.class, true);
skippable.put(FatalSkippableException.class, false);
skippable.put(ForceRollbackForWriteSkipException.class, true);
Map<Class<? extends Throwable>, Boolean> retryable = new HashMap<Class<? extends Throwable>, Boolean>();
retryable.put(FatalSkippableException.class, true);
retryable.put(ForceRollbackForWriteSkipException.class, true);
List<Class<CompositeItemStream>> streams = Arrays.asList(CompositeItemStream.class);
List<Class<DummyRetryListener>> retryListeners = Arrays.asList(DummyRetryListener.class);
List<Class<CompositeStepExecutionListener>> stepListeners = Arrays.asList(CompositeStepExecutionListener.class);
@@ -495,8 +500,8 @@ public class StepParserTests {
StepParserStepFactoryBean<?, ?> fb = (StepParserStepFactoryBean<?, ?>) ctx
.getBean("&stepWithListsOverrideWithEmpty");
assertEquals(0, getExceptionMap(fb, "skippableExceptionClasses").size());
assertEquals(0, getExceptionMap(fb, "retryableExceptionClasses").size());
assertEquals(1, getExceptionMap(fb, "skippableExceptionClasses").size());
assertEquals(1, getExceptionMap(fb, "retryableExceptionClasses").size());
assertEquals(0, ((ItemStream[]) ReflectionTestUtils.getField(fb, "streams")).length);
assertEquals(0, ((RetryListener[]) ReflectionTestUtils.getField(fb, "retryListeners")).length);
assertEquals(0, ((StepListener[]) ReflectionTestUtils.getField(fb, "listeners")).length);

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<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="http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<beans:import resource="common-context.xml" />
<job id="job">
<step id="s1">
<tasklet>
<chunk reader="reader" writer="writer" processor="processor" commit-interval="5" skip-limit="2" retry-limit="1" />
</tasklet>
</step>
</job>
</beans:beans>

View File

@@ -7,9 +7,19 @@
<beans:import resource="common-context.xml" />
<job id="job">
<step id="s1">
<step id="s1" next="s2">
<tasklet>
<chunk reader="reader" writer="writer" processor="processor" commit-interval="#{jobParameters['commit.interval']}"/>
<chunk reader="reader" writer="writer" processor="processor" commit-interval="#{jobParameters['commit.interval']}" />
</tasklet>
</step>
<step id="s2">
<tasklet>
<chunk reader="reader" writer="writer" processor="processor" commit-interval="1"
retry-limit="#{jobParameters['retry.interval']}">
<retryable-exception-classes>
<include class="java.lang.Exception" />
</retryable-exception-classes>
</chunk>
</tasklet>
</step>
</job>

View File

@@ -34,8 +34,8 @@
<tasklet>
<chunk reader="reader" writer="writer" commit-interval="5" skip-limit="5" retry-limit="3">
<skippable-exception-classes>
<include class="java.lang.NullPointerException"/>
<exclude class="org.springframework.dao.CannotAcquireLockException"/>
<include class="java.lang.IllegalArgumentException"/>
<exclude class="org.springframework.dao.ConcurrencyFailureException"/>
</skippable-exception-classes>
<retryable-exception-classes>
<include class="org.springframework.dao.DeadlockLoserDataAccessException"/>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<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="http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<beans:import resource="common-context.xml" />
<job id="job">
<step id="s1">
<tasklet>
<chunk reader="reader" writer="writer" processor="processor" commit-interval="5" skip-limit="2" retry-limit="1">
<skippable-exception-classes><include class="java.lang.Exception"/></skippable-exception-classes>
<retryable-exception-classes><include class="java.lang.Exception"/></retryable-exception-classes>
</chunk>
</tasklet>
</step>
</job>
</beans:beans>

View File

@@ -50,7 +50,7 @@ public class SimpleRetryPolicy implements RetryPolicy {
private volatile int maxAttempts;
private BinaryExceptionClassifier retryableClassifier = new BinaryExceptionClassifier(false);
private volatile BinaryExceptionClassifier retryableClassifier = new BinaryExceptionClassifier(false);
/**
* Create a {@link SimpleRetryPolicy} with the default number of retry
@@ -74,6 +74,13 @@ public class SimpleRetryPolicy implements RetryPolicy {
this.retryableClassifier = new BinaryExceptionClassifier(retryableExceptions);
}
/**
* @param retryableExceptions
*/
public void setRetryableExceptions(Map<Class<? extends Throwable>, Boolean> retryableExceptions) {
this.retryableClassifier = new BinaryExceptionClassifier(retryableExceptions);
}
/**
* Setter for retry attempts.
*
@@ -83,7 +90,7 @@ public class SimpleRetryPolicy implements RetryPolicy {
public void setMaxAttempts(int retryAttempts) {
this.maxAttempts = retryAttempts;
}
/**
* The maximum number of retry attempts before failure.
*