BATCH-1396: added step scope to retry and fixed bugs
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user