Using Spring Batch to parse date from file into LocalDateTime
Please use standard approach for data conversion:
- Data conversion is the responsibility of ConversionService.
- It's better to exposure FieldSetMapper as stand-alone bean due to assertions in afterPropertiesSet() method (it saves time to analyze misconfiguration).
- It's better to use FlatFileItemReaderBuilder.
You can easily extend and customize supported data types
@Configuration
@EnableBatchProcessing
public class SpringBatchDateParseConfig {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
public ConversionService testConversionService() {
DefaultConversionService testConversionService = new DefaultConversionService();
DefaultConversionService.addDefaultConverters(testConversionService);
testConversionService.addConverter(new Converter<String, LocalDateTime>() {
@Override
public LocalDateTime convert(String text) {
return LocalDateTime.parse(text, DateTimeFormatter.ISO_DATE_TIME);
}
});
return testConversionService;
}
@Bean
public FieldSetMapper<TestClass> testClassRowMapper(ConversionService testConversionService) {
BeanWrapperFieldSetMapper<TestClass> mapper = new BeanWrapperFieldSetMapper<>();
mapper.setConversionService(testConversionService);
mapper.setTargetType(TestClass.class);
return mapper;
}
@Bean
FlatFileItemReader<TestClass> testClassItemReader(FieldSetMapper<TestClass> testClassRowMapper) {
return new FlatFileItemReaderBuilder<TestClass>().delimited()
.delimiter(",")
.names(new String[]{"foo", "bar", "date"})
.linesToSkip(1)
.resource(new ClassPathResource("test.csv"))
.fieldSetMapper(testClassRowMapper)
.build();
}
@Bean
public Step step1(FieldSetMapper<TestClass> testClassRowMapper) {
return stepBuilderFactory.get("step1")
.<TestClass, TestClass>chunk(2)
.reader(testClassItemReader(testClassRowMapper))
.writer(testClassItemWriter())
.build();
}
@Bean
public Job job(Step step1) {
return jobBuilderFactory.get("job")
.start(step1)
.build();
}
@Bean
ItemWriter<TestClass> testClassItemWriter() {
return new ItemWriter<TestClass>() {
@Override
public void write(List<? extends TestClass> items) throws Exception {
for (TestClass TestClass : items) {
System.out.println(TestClass.toString());
}
}
};
}
}
I used the solution from Pavel because giving the Power to the ConversionService looks like a much cleaner solution to me.
The Date in my CSV is formatted like this : 10.01.20 so the pattern is : "dd.MM.yy" and i need to create LocalDate Objects from it.
Here is my custom ConversionService:
public ConversionService createConversionService() {
DefaultConversionService conversionService = new DefaultConversionService();
DefaultConversionService.addDefaultConverters(conversionService);
conversionService.addConverter(new Converter<String, LocalDate>() {
@Override
public LocalDate convert(String text) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yy");
return LocalDate.parse(text, formatter);
}
});
return conversionService;
}
Then i added this custom ConversionService to my BeanWrapperFieldSetMapper which is used in the ItemReader.
fieldSetMapper.setConversionService(createConversionService());
You can override method initBinder
of BeanWrapperFieldSetMapper<T>
:
public class BeanWrapperFieldSetMapperCustom<T> extends BeanWrapperFieldSetMapper<T> {
@Override
protected void initBinder(DataBinder binder) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
if (StringUtils.isNotEmpty(text)) {
setValue(LocalDate.parse(text, formatter));
} else {
setValue(null);
}
}
@Override
public String getAsText() throws IllegalArgumentException {
Object date = getValue();
if (date != null) {
return formatter.format((LocalDate) date);
} else {
return "";
}
}
});
}
}