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.
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.