2

I have created a simple single-step Spring batch job that reads items from a DB, processes them and writes the result to a csv. During runtime I end up with a

org.springframework.batch.item.WriterNotOpenException: Writer must be open before it can be written to

The relevant code:

@Configuration
@EnableBatchProcessing
@EnableAutoConfiguration
public class CleanEmailJob {

@Autowired
private JobBuilderFactory jobBuilderFactory;

@Autowired
private StepBuilderFactory stepBuilderFactory;

@Autowired
public DataSource dataSource;

@Bean
public ResourcelessTransactionManager transactionManager() {
    return new ResourcelessTransactionManager();
}

@Bean
public MapJobRepositoryFactoryBean mapJobRepositoryFactory(ResourcelessTransactionManager txManager)
        throws Exception {
    MapJobRepositoryFactoryBean factory = new MapJobRepositoryFactoryBean(txManager);
    factory.afterPropertiesSet();
    return factory;
}

@Bean
public JobRepository jobRepository(MapJobRepositoryFactoryBean factory) throws Exception {
    return factory.getObject();
}

@Bean
public JobExplorer jobExplorer(MapJobRepositoryFactoryBean factory) {
    return new SimpleJobExplorer(factory.getJobInstanceDao(), factory.getJobExecutionDao(),
            factory.getStepExecutionDao(), factory.getExecutionContextDao());
}

@Bean
public SimpleJobLauncher jobLauncher(JobRepository jobRepository) {
    SimpleJobLauncher launcher = new SimpleJobLauncher();
    launcher.setJobRepository(jobRepository);
    return launcher;
}


@Bean
public Job cleanEmailAddressesJob() throws Exception {
    return jobBuilderFactory.get("cleanEmailAddresses")
            .incrementer(new RunIdIncrementer())
            .start(processEmailAddresses())
            .build();
}

@Bean
public Step processEmailAddresses() throws UnexpectedInputException, ParseException, Exception {
    return stepBuilderFactory.get("processAffiliates")
            .<AffiliateEmailAddress, VerifiedAffiliateEmailAddress> chunk(10)
            .reader(reader())
            .processor(processor())     
            .writer(report())
            .build();
}

@Bean
public ItemWriter<VerifiedAffiliateEmailAddress> report(){
    FlatFileItemWriter<VerifiedAffiliateEmailAddress> reportWriter = new FlatFileItemWriter<VerifiedAffiliateEmailAddress>();
    reportWriter.setResource(new ClassPathResource("report.csv"));
    DelimitedLineAggregator<VerifiedAffiliateEmailAddress> delLineAgg = new DelimitedLineAggregator<VerifiedAffiliateEmailAddress>();
    delLineAgg.setDelimiter(",");
    BeanWrapperFieldExtractor<VerifiedAffiliateEmailAddress> fieldExtractor = new BeanWrapperFieldExtractor<VerifiedAffiliateEmailAddress>();
    fieldExtractor.setNames(new String[] {"uniekNr", "reason"});
    delLineAgg.setFieldExtractor(fieldExtractor);
    reportWriter.setLineAggregator(delLineAgg);
    reportWriter.setShouldDeleteIfExists(true);
    return reportWriter;
}

As described in the documentation I would expect the lifecycle events(open, close) are automatically taken care of since I am in a single threaded and single writer job?

Unibit
  • 89
  • 1
  • 5

2 Answers2

6

To elaborate on the comment left, Spring Batch will register any ItemStream implementations automatically when it finds them so that they will be automatically opened when the step begins. When using java config, Spring only knows what the return type is. Since you are returning ItemReader, we don't know that your implementation also implements ItemStream. When using java config, I usually recommend returning the implementation if it's known (instead of the interface). That allows Spring to introspect it fully. So in this example, returning FlatFileItemReader instead of ItemReader will fix the issue.

Michael Minella
  • 20,843
  • 4
  • 55
  • 67
0

Finally it turned out to be the Spring Dev Tools interfering with my batch application. The report.csv is in my classpath, when writing to the report file, Spring Dev Tools detects a change in my classpath and triggers a reload of the application causing the report.csv resource being closed...

spring.devtools.restart.enabled=false

in my application.properties made it work again

Unibit
  • 89
  • 1
  • 5