9

I'm trying to read a CSV file with dates with Spring Batch, but I am having trouble parsing a date into a LocalDateTime Object:

Field error in object 'target' on field 'date': rejected value [2017-07-20 04:15:25.0]; codes [typeMismatch.target.date,typeMismatch.date,typeMismatch.java.time.LocalDateTime,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [target.date,date]; arguments []; default message [date]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.time.LocalDateTime' for property 'date'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.time.LocalDateTime' for property 'date': no matching editors or conversion strategy found]

Main.java:

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringBatchDateParseConfig.class);

        JobLauncher jobLauncher = context.getBean(JobLauncher.class);
        Job job = context.getBean("job", Job.class);

        JobParameters jobParameters = new JobParametersBuilder().toJobParameters();

        try {
            JobExecution jobExecution = jobLauncher.run(job, jobParameters);
        }
        catch (Exception e) {
            e.printStackTrace();
        }

    }
}

SpringBatchDateParseConfig.java:

@Configuration
@EnableBatchProcessing
public class SpringBatchDateParseConfig {
    @Inject
    private JobBuilderFactory jobBuilderFactory;

    @Inject
    private StepBuilderFactory stepBuilderFactory;

    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                                 .<TestClass, TestClass>chunk(2)
                                 .reader(testClassItemReader())
                                 .writer(testClassItemWriter())
                                 .build();
    }

    @Bean
    public Job job(Step step1) {
        return jobBuilderFactory.get("job")
                .start(step1)
                .build();
    }

    @Bean
    FlatFileItemReader<TestClass> testClassItemReader() {
        FlatFileItemReader<TestClass> flatFileItemReader = new FlatFileItemReader<>();
        flatFileItemReader.setResource(new ClassPathResource("test.csv"));
        flatFileItemReader.setLinesToSkip(1);
        DefaultLineMapper defaultLineMapper = new DefaultLineMapper();
        DelimitedLineTokenizer delimitedLineTokenizer = new DelimitedLineTokenizer();
        delimitedLineTokenizer.setNames(new String[]{"foo", "bar", "date"});

        BeanWrapperFieldSetMapper<TestClass> fieldSetMapper = new BeanWrapperFieldSetMapper<>();
        fieldSetMapper.setTargetType(TestClass.class);

        defaultLineMapper.setLineTokenizer(delimitedLineTokenizer);
        defaultLineMapper.setFieldSetMapper(fieldSetMapper);
        flatFileItemReader.setLineMapper(defaultLineMapper);

        return flatFileItemReader;

    }

    @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());
                }
            }
        };
    }
}

TestClass.java:

public class TestClass {

    private String foo;
    private String bar;
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME, pattern = "yyyy-MM-dd H:m:s.S")
    private LocalDateTime date;

    public String getFoo() {
        return foo;
    }

    public void setFoo(String foo) {
        this.foo = foo;
    }

    public String getBar() {
        return bar;
    }

    public void setBar(String bar) {
        this.bar = bar;
    }

    public LocalDateTime getDate() {
        return date;
    }

    public void setDate(LocalDateTime date) {
        this.date = date;
    }
}

test.csv:

foo,bar,date
asdf,fdsa,2017-07-20 04:15:25.0
qwerty,ytrewq,2017-07-20 04:15:25.0

Am I missing something?

badjr
  • 2,166
  • 3
  • 20
  • 31
  • https://stackoverflow.com/questions/9059481/spring-batch-how-to-convert-string-from-file-to-date – Ben M Aug 22 '17 at 20:48
  • Possible duplicate of [Spring Batch - how to convert String from file to Date?](https://stackoverflow.com/questions/9059481/spring-batch-how-to-convert-string-from-file-to-date) – Ben M Aug 22 '17 at 20:49

3 Answers3

14

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 "";
                }
            }
        });
    }
}
Matt Tester
  • 4,663
  • 4
  • 29
  • 32
Corentin
  • 304
  • 6
  • 16
7

Please use standard approach for data conversion:

  1. Data conversion is the responsibility of ConversionService.
  2. It's better to exposure FieldSetMapper as stand-alone bean due to assertions in afterPropertiesSet() method (it saves time to analyze misconfiguration).
  3. 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());
                }
            }
        };
    }
}
Pavel
  • 2,557
  • 1
  • 23
  • 19
  • 3
    Hey @Pavel, thanks for your answer. I was trying to use your approach as it seems more clean than the accepted answer. Nevertheless, I got a circular dependencies error due to conflicting definition of beans. Just replacing `@Bean public ConversionService conversionService()` for a bean with a different name, for instance `@Bean public ConversionService myConversionService()` would avoid the conflicts. I leave it here in case anyone faces this problem. – fakefla Jun 24 '20 at 12:53
2

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());
eckad158
  • 385
  • 6
  • 19