Creé un trabajo por lotes Spring simple de un solo paso que lee elementos de una base de datos, los procesa y escribe el resultado en un csv. Durante el tiempo de ejecución termino con un
org.springframework.batch.item.WriterNotOpenException: Writer must be open before it can be written to
El código relevante:
@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; }
Como se describe en la documentación, esperaría que los eventos del ciclo de vida (abrir, cerrar) se atiendan automáticamente ya que estoy en un trabajo de un solo subproceso y un solo escritor.
Para profundizar en el comentario dejado, Spring Batch registrará cualquier implementación de ItemStream
automáticamente cuando las encuentre para que se abran automáticamente cuando comience el paso. Al usar la configuración de Java, Spring solo sabe cuál es el tipo de retorno. Dado que está devolviendo ItemReader
, no sabemos si su implementación también implementa ItemStream
. Cuando uso la configuración de Java, generalmente recomiendo devolver la implementación si se conoce (en lugar de la interfaz). Eso le permite a Spring hacer una introspección completa. Entonces, en este ejemplo, devolver FlatFileItemReader
en lugar de ItemReader
solucionará el problema.
Finalmente resultó que Spring Dev Tools interfería con mi aplicación por lotes. El report.csv está en mi classpath, cuando se escribe en el archivo de informe, Spring Dev Tools detecta un cambio en mi classpath y activa una recarga de la aplicación, lo que hace que se cierre el recurso report.csv...
spring.devtools.restart.enabled=false
en mi application.properties lo hizo funcionar de nuevo