After finding an accepted answer from a Spring Batch dev here, as well as the accompanying JavaConfig code below that, I am still left a bit confused as to how to use stepScope(). I am trying to scope the multiResourceItemReader below, but simply adding the bean definition of stepScope() to the top or bottom of the file causes this error:
Exception encountered during context initialization - cancelling refresh attempt:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'readFiles' defined in class path resource [com/onlinephotosubmission/csvImporter/service/BatchJobService.class]:
Bean instantiation via factory method failed;
nested exception is org.springframework.beans.BeanInstantiationException:
Failed to instantiate [java.lang.Object]: Factory method 'readFiles' threw exception;
nested exception is java.lang.NullPointerException:
Cannot invoke method get() on null object
All I know is that stepScope() has to be in a @Configuration file, other than that, I'm thoroughly confused as to what needs to be done.
BatchJobService.groovy
@Configuration
@EnableBatchProcessing
class BatchJobService {
@Autowired
JobBuilderFactory jobBuilderFactory
@Autowired
StepBuilderFactory stepBuilderFactory
@Autowired
JobLauncher jobLauncher
@Autowired
AfterJobListener afterJobListener
// Set values from properties file
@Value('${input.directory:file:inputs/*.csv}')
Resource[] resources
@Value('${report.directory:output}')
String reportsDir
@Value('${completed.directory:completed}')
String completedDir
@Value('${report.name.prepend:people}')
String prependName
@Value('${timestamp.format:dd_MM_yyyy_HH_mm_ss}')
String timestampFormat
// End set properties
@Bean
StepScope stepScope() {
final StepScope stepScope = new StepScope()
stepScope.setAutoProxy(true)
return stepScope
}
@Bean
Job readFiles() {
return jobBuilderFactory
.get("readFiles")
.incrementer(new RunIdIncrementer())
.flow(step1())
.end()
.listener(afterJobListener)
.build()
}
@Bean
Step step1() {
return stepBuilderFactory
.get("step1")
//NOTE: may need to adjust chunk size larger (say 1000 to take all transacions at once)
// or smaller (say 1 to take each transaction individually).
// Bigger is usually better, though.
.<Person, Person>chunk(1000)
.reader(multiResourceItemReader())
.processor(modifier())
.writer(writer())
.build()
}
@Bean
MultiResourceItemReader<Person> multiResourceItemReader() {
MultiResourceItemReader<Person> resourceItemReader = new MultiResourceItemReader<Person>()
resourceItemReader.setResources(resources)
resourceItemReader.setDelegate(reader())
return resourceItemReader
}
@Bean
FlatFileItemReader<Person> reader() {
FlatFileItemReader<Person> reader = new FlatFileItemReader<Person>()
reader.setLinesToSkip(1) //skips header line
reader.setLineMapper(new DefaultLineMapper()
{{
setLineTokenizer(new DelimitedLineTokenizer(",")
{{
setNames(["email", "identifier"] as String[])
}})
setFieldSetMapper(new BeanWrapperFieldSetMapper<Person>() // BeanWrapperFieldSetMapper maps the line token values to a POJO directly by name
{{
setTargetType(Person.class)
}})
}})
return reader
}
@Bean
PersonItemProcessor modifier(){
return new PersonItemProcessor()
}
@Bean
FlatFileItemWriter<Person> writer() {
FlatFileItemWriter<Person> writer = new FlatFileItemWriter<>()
writer.setAppendAllowed(true)
writer.setResource(new FileSystemResource(reportsDir + "/" + prependName + getTime() + ".csv"))
writer.setLineAggregator(new DelimitedLineAggregator<Person>()
{{
setDelimiter(",")
setFieldExtractor(new BeanWrapperFieldExtractor<Person>()
{{
setNames(["status", "email", "identifier"] as String[])
}})
}})
return writer
}
}