Pranav C Balan Pranav C Balan - 6 months ago 144
Java Question

Custom ItemReader with spring-data-jpa

I'm creating a Spring batch project using existing entities and repositories. I need to use custom

ItemReader
for the job which reads data using the existing jpa repositories.

Custom reader

public class InMemoryReader implements ItemReader<Product> {

@Autowired
private ProductService productService;


private int nextStudentIndex;
private List<Product> studentData;

public InMemoryReader() {
initialize();
}

private void initialize() {
studentData = new ArrayList<Product>();
studentData.add(new Product("hi"));

for (Product p : productService.get())
studentData.add(p);
nextStudentIndex = 0;
}

@Override
public Product read() throws Exception {
Product nextStudent = null;

if (nextStudentIndex < studentData.size()) {
nextStudent = studentData.get(nextStudentIndex);
nextStudentIndex++;
}

return nextStudent;
}
}


But I can't Autowire the
ProductService
in the
itemreader
. It throwing error as follows:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'reader' defined in class path resource [wariyum/sb/emailNotifier/BatchConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.batch.item.ItemReader]: Factory method 'reader' threw exception; nested exception is java.lang.NullPointerException
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:599)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1123)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1018)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:510)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:305)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:301)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:196)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:834)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:537)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:689)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:321)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:969)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:958)
at wariyum.sb.emailNotifier.Application.main(Application.java:15)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.batch.item.ItemReader]: Factory method 'reader' threw exception; nested exception is java.lang.NullPointerException
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:189)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:588)
... 16 common frames omitted
Caused by: java.lang.NullPointerException: null
at wariyum.sb.emailNotifier.service.InMemoryReader.initialize(InMemoryReader.java:31)
at wariyum.sb.emailNotifier.service.InMemoryReader.<init>(InMemoryReader.java:24)
at wariyum.sb.emailNotifier.BatchConfiguration.reader(BatchConfiguration.java:41)
at wariyum.sb.emailNotifier.BatchConfiguration$$EnhancerBySpringCGLIB$$df186b4e.CGLIB$reader$0(<generated>)
at wariyum.sb.emailNotifier.BatchConfiguration$$EnhancerBySpringCGLIB$$df186b4e$$FastClassBySpringCGLIB$$724d6a16.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:318)
at wariyum.sb.emailNotifier.BatchConfiguration$$EnhancerBySpringCGLIB$$df186b4e.reader(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162)
... 17 common frames omitted


Batch configuration file

@Configuration
@EnableBatchProcessing
public class BatchConfiguration {


@Autowired
public JobBuilderFactory jobBuilderFactory;

@Autowired
private DataSource dataSource;

@Autowired
public StepBuilderFactory stepBuilderFactory;

@Bean
public ItemReader<Product> reader() throws Exception {
return new InMemoryReader();
}


@Bean
public ProductItemProcessor processor() {
return new ProductItemProcessor();
}

@Bean
public ItemWriter<Product> writer() {
return new ItemWriter<Product>() {
@Override
public void write(List<? extends Product> items) throws Exception {

}
};
}
// end::readerwriterprocessor[]

// tag::listener[]

@Bean
public JobExecutionListener listener() {
return null;
}

// end::listener[]

// tag::jobstep[]

@Bean
public Job importPerson(JobBuilderFactory jobs, Step s1) {

return jobs.get("import")
.incrementer(new RunIdIncrementer()) // because a spring config bug, this incrementer is not really useful
.flow(s1)
.end()
.build();
}

@Bean
public Step step1() throws Exception {
return stepBuilderFactory.get("step1")
.<Product, Product>chunk(10)
.reader(reader())
.processor(processor())
.writer(writer())
.build();
}
}


Product service implementation

@Service("productService")
public class ProductServiceImpl implements ProductService {

private ProductRepository repository;

@Autowired
public ProductServiceImpl(ProductRepository repository) {
this.repository = repository;
}

@Override
public List<Product> get() {
List<Product> list = new ArrayList<Product>();
for (Product p : repository.findAll())
list.add(p);
return list;
}
}

Answer

You are calling initialize() from the constructor i.e. before the ProductService dependency has been wired.

Remove the call to initialize() from the constructor and instead have Spring invoke it after the dependencies have been wired by annotating it with @PostConstruct or using one of the other methods outlined here:

How to call a method after bean initialization is complete?