Falha ao converter o valor da propriedade do tipo 'java.lang.String' para o tipo necessário 'java.sql.Date'

Oct 08 2020

Escrevi um trabalho em lote do Spring que lê um CSV e grava no banco de dados do SQL server. O arquivo CSV possui alguns campos do tipo Datee TimeStamp. Estou usando FlatFileItemReader e JdbcBatchItemWriter.

O formato de data em meu CSV é aaaa-MM-dd e o formato TimeStamp é aaaa-MM-dd HH: mm: ss.SSSSSS

NOTA: Estou usando java.sql.Date e TimeStamp, NÃO java.util.Date e TimeStamp

Classe de usuário:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User{
    private String firstName;
    private String lastName;
    private Date dateOfBirth;
    private Date dateOfJoining;
    private TimeStamp timeStampReg;
...

Eu uso a mesma classe de modelo Userpara ler do banco de dados em uma etapa diferente.

O leitor na classe de configuração se parece com:

@Configuration
public class BatchConfigClass{

    
    //Step 1
    @Bean
    public FlatFileItemReader<User> itemReader() {

        FlatFileItemReader<User> flatFileItemReader = new FlatFileItemReader<>();
        flatFileItemReader.setResource(inputResource);
        flatFileItemReader.setName("CSV-Reader");
        flatFileItemReader.setLineMapper(lineMapper());
        return flatFileItemReader;
    }

    @Bean
    public LineMapper<User> lineMapper() {

        DefaultLineMapper<User> defaultLineMapper = new DefaultLineMapper<>();
        DelimitedLineTokenizer lineTokenizer = new DelimitedLineTokenizer();

        lineTokenizer.setDelimiter(",");
        lineTokenizer.setStrict(false);
        lineTokenizer.setNames(new String[]{"firstName", "lastName", "dateOfBirth", "dateOfJoining","timeStampReg"});

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

        defaultLineMapper.setLineTokenizer(lineTokenizer);
        defaultLineMapper.setFieldSetMapper(fieldSetMapper);

        return defaultLineMapper;
    }
...

Ao executar o trabalho, recebo os erros:

Parsing error at line: 1 in resource=Path to the CSV file

Failed to convert property value of type 'java.lang.String' to required type 'java.sql.Date' for property 'dateOfBirth';

Field error in object 'target' on field 'dateOfBirth': rejected value [1998-12-31]; codes [typeMismatch.target.dateOfBirth,typeMismatch.dateOfBirth,typeMismatch.java.sql.Date,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [target.dateOfBirth,dateOfBirth]; arguments []; default message [dateOfBirth]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.sql.Date' for property 'dateOfBirth'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.sql.Date' for property 'dateOfBirth': no matching editors or conversion strategy found]

Ele dá o mesmo erro para outros campos de data e carimbo de data / hora ( dateOfJoininge timeStampReg)

EDITAR:

Resolvi o erro para os Datecampos de tipo pela seguinte solução, mas ainda estou recebendo o mesmo erro para o TimeStampcampo de tipo.

Eu criei um custom BeanWrapperFieldSetMappere substituí seu initBinderpara adicionar um CustomDateEditor.

public class MyCustomBeanWrapperFieldSetMapper<User> extends BeanWrapperFieldSetMapper<FSMChequePayment> {

    @Override
    protected void initBinder(DataBinder binder) {
        CustomDateEditor editor = new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true);
        binder.registerCustomEditor(Date.class, editor);

        CustomDateEditor editorTimeStamp = new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSS"), true);
        binder.registerCustomEditor(TimeStamp.class,editorTimeStamp);
        
    }
}

e usar seu objeto em vez do real BeanWrapperFieldSetMapperno flatFileItemReaderbean, substituindo-o por:

MyCustomBeanWrapperFieldSetMapper<User> fieldSetMapper = new MyCustomBeanWrapperFieldSetMapper<>();

PS: O editor personalizado não funcionou para java.sql.Datee java.sql.TimeStamp. Então eu tive que mudar para java.util.Date. Isso resolveu para o Datetipo, mas o erro ainda persiste para o TimeStamptipo.

Respostas

2 ThomasKåsene Oct 08 2020 at 21:27

Parece que seu lote só consegue dividir suas linhas em Stringvalores, mas não consegue converter os tipos mais exóticos. O JavaDoc paraBeanWrapperFieldSetMapper diz:

Para personalizar a maneira como os valores de FieldSet são convertidos no tipo desejado para injeção no protótipo, existem várias opções. Você pode injetar instâncias de PropertyEditor diretamente por meio da propriedade customEditors ou pode substituir os métodos createBinder (Object) e initBinder (DataBinder), ou pode fornecer uma implementação FieldSet personalizada. Você também pode usar um ConversionService para converter para o tipo desejado por meio da propriedade conversionService.

Sugiro experimentar algumas dessas alternativas com base em quais atendem melhor às suas necessidades.

EDIT: Uma solução possível pode ser simplesmente usar a ApplicationConversionServiceclasse, que Spring Boot fornece fora da caixa. Eu configurei um exemplo funcional no GitHub se você quiser dar uma olhada em minha solução e, mais importante, em minhas suposições sobre seu projeto. Observe que estou usando as java.sql.*classes em meu projeto de exemplo porque ApplicationConversionServiceaparentemente funciona bem com elas.

@Bean
FieldSetMapper<User> fieldSetMapper() {
    BeanWrapperFieldSetMapper<User> fieldSetMapper = new BeanWrapperFieldSetMapper<>();
    fieldSetMapper.setTargetType(User.class);
    fieldSetMapper.setConversionService(ApplicationConversionService.getSharedInstance());
    return fieldSetMapper;
}