2

See below for 1 update

I'm trying to inject a Service or DAO bean into my spring-batch ItemReader. However the injected reference is NULL. I tried this using @Autowire and explicit bean configuration in the XML file.

The job is being run from a spring-mvc application. This ItemReader requires DB access to load a list of ID's to process. The reader itereates through this list and the ItemWriter makes the necessary changes on the database for each ID using my existing Service and DAO Layers.

Here's my configuration (it is defined on a separate spring-batch.xml file included on the web.xml after the application context XML file.)

Also, in this version I'm trying to inject the JobService bean into the ItemReader using the XML configuration.

The general problem here is: how to inject other DAO's and/or Services into Item Readers and Writers?

<beans:bean id="jobRepository" class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
    <beans:property name="dataSource" ref="dataSourceJNDI" />
    <beans:property name="transactionManager" ref="transactionManager" />
    <beans:property name="databaseType" value="mysql" />
</beans:bean>

<beans:bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
    <beans:property name="jobRepository" ref="jobRepository" />
</beans:bean>

<beans:bean id="jobRegistry" class="org.springframework.batch.core.configuration.support.MapJobRegistry" />

<beans:bean id="myItemReader" class="my.MyItemReader" scope="step">
    <beans:property name="jobService">
        <beans:bean class="my.JobService"></beans:bean>
    </beans:property>
</beans:bean>

<job id="assignLeadBatch" restartable="false" job-repository="jobRepository">
    <step id="step1">
        <tasklet transaction-manager="dbOpsTransactionManager">
            <chunk reader="myItemReader" writer="myItemWriter" commit-interval="10" />
        </tasklet>
    </step>
</job>

<beans:bean class="org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor">
    <beans:property name="jobRegistry" ref="jobRegistry" />
</beans:bean>

ItemReader:

@Scope(value = "step", proxyMode = ScopedProxyMode.INTERFACES)
@Component
public class MyItemReader implements ItemReader<Integer> {

    JobService jobService;

    private List<Integer> itemsList;

    @Autowired
    public AssignLeadsJobItemReader(@Value("#{jobParameters['jobKey']}") final String jobKey) {

        MyJob alj = jobService.loadByKey(jobKey);

        itemsList = new ArrayList<AssignLeadsJobItem>();

        for(Integer i : myJob.getIdList()) {            
            itemsList.add(i);
        }
    }

    @Override
    public Integer read(){
        if(itemsList == null || itemsList.size() == 0) {
            return null;
        } else {
            return itemsList.remove(0);
        }
    }

    public JobService getJobService() {
        return jobService;
    }

    public void setJobService(JobService jobService) {
        this.jobService = jobService;
    }
}

Job Service:

@Service
@Transactional
public class JobService {
    @Autowired
    protected JobDAO jobDao;

    @Autowired
    protected SpringBatchService springBatchService;

    public JobExecution startJob(String jobName, String jobKey) {    
            // Defined in a different Class without transactions.
            return springBatchService.startJob("job_name", jobKey);
    }

    public MyJob loadByKey(String jobKey) {
            return jobDao.loadByKey(jobKey);
    }
}

UPDATE 1 I noticed that the error was caused by calling the reference on the ItemReader constructor. I changed the code thusly:

@Scope(value = "step", proxyMode = ScopedProxyMode.INTERFACES)
@Component
public class MyItemReader implements ItemReader<Integer> {
    private JobService jobService;
    private List<Integer> itemsList;
    private String jobKey;

    @Autowired
    public MyItemReader(@Value("#{jobParameters['jobKey']}") final String jobKey) {
        this.jobKey = jobKey;
        this.itemsList = null;
    }

    @Override
    public Integer read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
        // ugly ugly ugly
        if (itemsList == null) {
            itemsList = new ArrayList<Integer>();

            MyJob jobData = jobService.loadByKey(jobKey);

            for (Integer i : jobData.getIdList()) {
                itemsList.add(i);
            }
        }

        if (itemsList.isEmpty()) {
            return null;
        } else {
            return itemsList.remove(0);
        }
    }
}

Is there a way to put that ugly initialization in the constructor?

tggm
  • 973
  • 2
  • 15
  • 41
  • Declare job.service as singleton and wire to reader using property-ref – Luca Basso Ricci Nov 10 '16 at 06:39
  • Thanks @Luca. I changed the code according to yor suggestion. I then further noticed that the problem is that I'm calling my DAO on the constructor of the ItemReader. I changed things to initialize the arrayList (from the DAO) on the read() method; so it first checks if List == null, initializes it if so and then returns elements. But this seems awkward because this initialization should be on the constructor. Is there a way to make it work from there ? Also, please make a answer of your comment so I can accept it! – tggm Nov 10 '16 at 12:31

1 Answers1

2

I'd do the following:

  • Autowire the JobService
  • Make itemList and jobKey into final class variables
  • Use InitializingBean interface to populate the list

Now your reader will look something like this:

@Scope(value = "step", proxyMode = ScopedProxyMode.INTERFACES)
@Component
public class MyItemReader implements ItemReader<Integer>, InitializingBean {

    private final List<Integer> itemsList = new ArrayList<AssignLeadsJobItem>();

    @Autowired
    private JobService jobService;

    private final String jobKey;

    @Autowired
    public MyItemReader(@Value("#{jobParameters['jobKey']}") final String jobKey) {
        this.jobKey = jobKey;
    }

    @Override
    public Integer read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
        if(itemsList.size() == 0) {
            return null;
        } else {
            return itemsList.remove(0);
        }
    }

    @Override
    public void afterPropertiesSet() {
        MyJob myJob = jobService.loadByKey(jobKey);
        for(Integer id : myJob.getIdList()) {            
            itemsList.add(id);
        }
    }
}
Dean Clark
  • 3,770
  • 1
  • 11
  • 26
  • I had similar issue with null pointer when accessing autowired serviced in my reader. Can you explain why I need to do the service call from afterPropertiesSet instead of inside read? This is working for me now but not sure why. – Micho Rizo Feb 28 '18 at 05:40
  • Implementing InitializingBean on the reader also seems to skip the processor and writer unless I also implement InitializingBean on those steps. Is that expected behavior? – Micho Rizo Feb 28 '18 at 06:11
  • Not at all, nor is it what I've ever seen in the past. There must be something else going on. I'm assuming your `itemsList` is empty, so the step believes it has no work to do. – Dean Clark Mar 02 '18 at 14:38