spring-dataspring-batchspring-jdbc

JdBcPagingItemReader fails with Null NamedJdbcParameter for Step Configuration


I am attempting to create a simple Step for a batch process which takes ProductDetail information and other information from the item processor to create and write the new ProductResult to a Mongodb database. I am attempting to use a JdbcPagingItemReader for reading the data from a database. However, when I start the job it fails with an error

java.lang.NullPointerException: Cannot invoke "org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.getJdbcOperations()" because "this.namedParameterJdbcTemplate" is null. 

I also tested this with another reader and it works, so it is only the JdbcPagingItemReader that's the issue.

I don't know why the NamedParameterJdbcTemplate is needed since I have provided the select and from values and a configured row mapper.

Here is my code


    import com.datacom.mapper.ProductDetailRowMapper;
    import com.datacom.model.ProductDetail;
    import lombok.RequiredArgsConstructor;
    import org.springframework.batch.core.Step;
    import org.springframework.batch.core.repository.JobRepository;
    import org.springframework.batch.core.step.builder.StepBuilder;
    import org.springframework.batch.item.ItemStreamReader;
    import org.springframework.batch.item.ItemWriter;
    import org.springframework.batch.item.data.MongoItemWriter;
    import org.springframework.batch.item.data.builder.MongoItemWriterBuilder;
    import org.springframework.batch.item.database.JdbcPagingItemReader;
    import org.springframework.batch.item.database.Order;
    import org.springframework.batch.item.database.support.OraclePagingQueryProvider;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.mongodb.core.MongoOperations;
    import org.springframework.transaction.PlatformTransactionManager;
    
    import javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.Map;
    
    @Configuration
    @RequiredArgsConstructor
    public class ProductDetailConfigStep {
    
        @Value("${db.pageSize}")
        int pageSize;
    
        final MongoOperations mongoOperations;
    
        final @Qualifier("productsDataSource") DataSource dataSource;
    
        final PlatformTransactionManager transactionManager;
    
    
        final JobRepository jobRepository;
    
        @Value("${batch.chunkSize}") int chunkSize;
    
        final ProductResultProcessor productResultProcessor;
    
        public ItemStreamReader<ProductDetail> itemReader() {
            JdbcPagingItemReader<ProductDetail> itemReader = new JdbcPagingItemReader<>();
            itemReader.setDataSource(productsDataSource);
            itemReader.setFetchSize(chunkSize);
            OraclePagingQueryProvider queryProvider = new OraclePagingQueryProvider();
            queryProvider.setSelectClause(JDBCConstants.SELECT_QUERY);
            queryProvider.setFromClause(JDBCConstants.FROM_CLAUSE_QUERY);
            itemReader.setRowMapper(new ProductDetailRowMapper());
            final Map<String, Order> sortKeys = new HashMap<>();
            itemReader.setParameterValues(new HashMap<>());
            sortKeys.put("product_id", Order.ASCENDING);
            queryProvider.setSortKeys(sortKeys);
            itemReader.setQueryProvider(queryProvider);
            itemReader.setPageSize(pageSize);
            itemReader.setPageSize(chunkSize);
            return itemReader;
        }
    
        public ItemWriter<ProductResult> itemWriter() {
            var writer = new MongoItemWriterBuilder<ProductResult>()
                    .collection(Documents.PRODUCT_CATALOG_COLLECTION)
                    .mode(MongoItemWriter.Mode.INSERT)
                    .template(mongoOperations)
                    .build();
            writer.setTemplate(mongoOperations);
            return writer;
        }
    
        @Bean("productProcessingDetailStep")
        public Step productProcessingDetailStep() {
            return new StepBuilder("productCreationStep", jobRepository)
                    .<ProductDetail, ProductResult>chunk(chunkSize, transactionManager)
                    .reader(itemReader())
                    .processor(productResultProcessor)
                    .writer(itemWriter())
                    .build();
        }
    }

What am I missing the configuration of the JdbcPagingItemReader looks fine to me.


Solution

  • The field namedParameterJdbcTemplate is null because the method afterProperties of the reader is not being called.

    If you expose the reader as a bean, then Spring will call this method for you. But you can also do this yourself if there is no other need for the reader to be a bean.

    It should work as expected if you change your code like this:

    public ItemStreamReader<ProductDetail> itemReader() throws Exception {
        ...
        itemReader.afterPropertiesSet();
        return itemReader;
    }
    

    Please note the throws Exception in the method signature. Alternatively, you can wrap the call to afterPropertiesSet in an appropriate try-catch block.