Nie można przekształcić wartości właściwości typu „java.lang.String” na wymagany typ „java.sql.Date”

Oct 08 2020

Napisałem wiosenne zadanie wsadowe, które czyta CSV i zapisuje w bazie danych SQL Server. Plik CSV zawiera pola typu Datei TimeStamp. Używam FlatFileItemReader i JdbcBatchItemWriter.

Format daty w moim pliku CSV to rrrr-MM-dd, a format znacznika czasu to rrrr-MM-dd GG: mm: ss.SSSSSS

UWAGA: używam java.sql.Date i TimeStamp, NIE java.util.Date i TimeStamp

Klasa użytkownika:

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

Używam tej samej klasy modelu Userdo odczytu z bazy danych w innym kroku.

Czytnik w klasie konfiguracji wygląda następująco:

@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;
    }
...

Podczas wykonywania pracy otrzymuję błędy:

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]

Daje ten sam błąd dla innych pól daty i znacznika czasu ( dateOfJoiningi timeStampReg)

EDYTOWAĆ:

Postanowiłem błędu dla Datepól typu przez następujące rozwiązanie, ale jestem wciąż otrzymuję ten sam błąd na TimeStamppolu Typ.

Utworzyłem custom BeanWrapperFieldSetMapperi nadpisałem, initBinderaby dodać 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);
        
    }
}

i użyj jego obiektu zamiast rzeczywistego BeanWrapperFieldSetMapperw flatFileItemReaderziarnie, zastępując go:

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

PS: Edytor niestandardowy nie działał dla java.sql.Datei java.sql.TimeStamp. Więc musiałem się przełączyć java.util.Date. To rozwiązało problem dla Datetypu, ale błąd nadal występuje dla TimeStamptypu.

Odpowiedzi

2 ThomasKåsene Oct 08 2020 at 21:27

Wygląda na to, że Twoja partia może tylko podzielić linie na Stringwartości, ale nie może przekonwertować bardziej egzotycznych typów. JavaDoc dlaBeanWrapperFieldSetMapper mówi:

Istnieje kilka możliwości dostosowania sposobu, w jaki wartości FieldSet są konwertowane na żądany typ do wstrzyknięcia do prototypu. Wystąpienia PropertyEditor można wstrzykiwać bezpośrednio za pomocą właściwości customEditors lub można przesłonić metody createBinder (Object) i initBinder (DataBinder) lub udostępnić niestandardową implementację FieldSet. Możesz również użyć ConversionService, aby przekonwertować na żądany typ za pomocą właściwości conversionService.

Proponuję poeksperymentować z niektórymi z tych alternatyw w oparciu o te, które najlepiej odpowiadają Twoim potrzebom.

EDYCJA: Możliwym rozwiązaniem może być po prostu użycie ApplicationConversionServiceklasy, którą Spring Boot zapewnia po wyjęciu z pudełka. Założyłem przykład pracę na GitHub jeśli chcesz spojrzeć na moje rozwiązanie, a co ważniejsze, moje założenia dotyczące projektu. Zauważ, że używam java.sql.*klas w moim przykładowym projekcie, ponieważ ApplicationConversionServicepozornie działa dobrze z nimi.

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