1

I have a batch job where i am using ScriptBatch.3.0.x version.

My use-case is to retry the job incase of any intermediate failures in between.

I am using the Chunk based processing and StepBuilderFactory for a job. I could not see any difference by adding the retry in it.

    return stepBuilderFactory.get("ValidationStepName")
            .<Long, Info> chunk(10)
            .reader(.....)
            .processor(.....)
    //        .faultTolerant()
    //        .retryLimit(5)
    //        .retryLimit(5).retry(Exception.class)
            .writer(......)
            .faultTolerant()
            .retryLimit(5)
            //.retryLimit(5).retry(Exception.class)
            .transactionManager(jpaTransactionManager())
            .listener(new ChunkNotificationListener())
            .build();

Not sure i am missing something here, I am expecting here that adding retryLimit() will retry the same chunk for n number of time on getting any exception

TikTik
  • 347
  • 2
  • 8
  • 22

1 Answers1

2

I am expecting here that adding retryLimit() will retry the same chunk for n number of time on getting any exception

If you specify a retry limit, you need to specify which exceptions to retry. Otherwise you would have an IllegalStateException with the message: If a retry limit is provided then retryable exceptions must also be specified.

EDIT:

Point 1 : The following test is passing with version 3.0.9:

import java.util.Arrays;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.tasklet.TaskletStep;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.batch.item.support.ListItemWriter;
import org.springframework.transaction.PlatformTransactionManager;

@RunWith(MockitoJUnitRunner.class)
public class TestRetryConfig {

    @Rule
    public ExpectedException expectedException = ExpectedException.none();
    @Mock
    private JobRepository jobRepository;
    @Mock
    PlatformTransactionManager transactionManager;

    @Test
    public void testRetryLimitWithoutException() {
        expectedException.expect(IllegalStateException.class);
        expectedException.expectMessage("If a retry limit is provided then retryable exceptions must also be specified");

        StepBuilderFactory stepBuilderFactory = new StepBuilderFactory(jobRepository, transactionManager);

        TaskletStep step = stepBuilderFactory.get("step")
                .<Integer, Integer>chunk(2)
                .reader(new ListItemReader<>(Arrays.asList(1, 2, 3)))
                .writer(new ListItemWriter<>())
                .faultTolerant()
                .retryLimit(3)
                .build();
    }
}

It shows that if you specify a retry limit without the exception type(s) to retry, the step configuration should fail.

Point 2: The following sample shows that the declared exception type is retried as expected (tested with version 3.0.9 too):

import java.util.Arrays;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableBatchProcessing
public class MyJob {

    @Autowired
    private JobBuilderFactory jobs;

    @Autowired
    private StepBuilderFactory steps;

    @Bean
    public ItemReader<Integer> itemReader() {
        return new ListItemReader<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
    }

    @Bean
    public ItemWriter<Integer> itemWriter() {
        return items -> {
            for (Integer item : items) {
                System.out.println("item = " + item);
                if (item.equals(7)) {
                    throw new Exception("Sevens are sometime nasty, let's retry them");
                }
            }
        };
    }

    @Bean
    public Step step() {
        return steps.get("step")
                .<Integer, Integer>chunk(5)
                .reader(itemReader())
                .writer(itemWriter())
                .faultTolerant()
                .retryLimit(3)
                .retry(Exception.class)
                .build();
    }

    @Bean
    public Job job() {
        return jobs.get("job")
                .start(step())
                .build();
    }

    public static void main(String[] args) throws Exception {
        ApplicationContext context = new AnnotationConfigApplicationContext(MyJob.class);
        JobLauncher jobLauncher = context.getBean(JobLauncher.class);
        Job job = context.getBean(Job.class);
        jobLauncher.run(job, new JobParameters());
    }

}

it prints:

item = 1
item = 2
item = 3
item = 4
item = 5
item = 6
item = 7
item = 6
item = 7
item = 6
item = 7

item 7 is retried 3 times and then the step fails as expected.

I hope this helps.

Mahmoud Ben Hassine
  • 28,519
  • 3
  • 32
  • 50
  • 1
    Two thinks, 1. I didn't get any exception without providing the exception, exception occurs one and job completed 2 .Even i provide the exception as .retryLimit(5).retry(Exception.class) it also didn't help – TikTik Aug 27 '18 at 21:52
  • Mahmoud, thanks for the sample snippet, It helps me. I have follow-up question on this, is the above implementation will be a state less implementation or it will be stateful one – TikTik Aug 28 '18 at 20:48
  • Sorry I'm not sure I understand the question, what do you mean by stateless or stateful? – Mahmoud Ben Hassine Aug 28 '18 at 21:05
  • 1
    While retrying it i will maintain the intermediate state or not? your sample code uses only the ItemReader &ItemWriter. Let us assume we have ItemReader, ItemProcessor and ItemWritter. If there is any intermediate failures in ItemWritter, step will execute the ItemProcessor and ItemWritter (If my understanding is correct), in this case the above code snippet will maintain State where it get the exception or not Please refer https://docs.spring.io/spring-batch/3.0.x/reference/html/retry.html#statelessRetry for more clarity – TikTik Aug 28 '18 at 21:19
  • yes, if there is an error, both the itemProcessor and itemWriter will be re-executed. You can add a simple processor (that prints out the item to the standard output) to the sample and see how it behaves. – Mahmoud Ben Hassine Aug 28 '18 at 21:43