Question

I need to make a customized reader by extending JdbcCursorItemReader. I am doing it as below:

package sample.peektry;

import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component("myReader")
public class MyReader implements ItemReader<MyBean> {

    @Autowired
    MyPeekableReader myPeekableReader;

    public MyPeekableReader getMyPeekableReader() {
        return myPeekableReader;
    }

    public void setMyPeekableReader(MyPeekableReader myPeekableReader) {
        this.myPeekableReader = myPeekableReader;
    }

    @Override
    public MyBean read() throws Exception, UnexpectedInputException,
    ParseException, NonTransientResourceException {
        System.out.println(" I will peek and read... :)");
        return null;
    }

}

package sample.peektry;

import org.springframework.batch.item.support.SingleItemPeekableItemReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component("myPeekableReader")
public class MyPeekableReader extends SingleItemPeekableItemReader<MyBean> {

    @Autowired
    private MyJdbcReader myJdbcReader;

    public MyJdbcReader getMyJdbcReader() {
        return myJdbcReader;
    }

    public void setMyJdbcReader(MyJdbcReader myJdbcReader) {
        this.myJdbcReader = myJdbcReader;
    }


}

Further, MyRowMapper implements RowMapper and has @Component("myRowMapper"). Similarly, MyPrepStmntSetter implements PreparedStatementSetter and has @Component("myPrepStmntSetter")

package sample.peektry;

import javax.annotation.Resource;
import javax.sql.DataSource;
import org.springframework.batch.item.database.JdbcCursorItemReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component("myJdbcReader")
public class MyJdbcReader extends JdbcCursorItemReader<MyBean> {

    @Resource
    DataSource dataSource;

    @Autowired
    MyRowMapper myRowMapper;

    @Autowired
    MyPrepSetter myPrepSetter;

    public DataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public MyRowMapper getMyRowMapper() {
        return myRowMapper;
    }

    public void setMyRowMapper(MyRowMapper myRowMapper) {
        this.myRowMapper = myRowMapper;
    }

    public MyPrepSetter getMyPrepSetter() {
        return myPrepSetter;
    }

    public void setMyPrepSetter(MyPrepSetter myPrepSetter) {
        this.myPrepSetter = myPrepSetter;
    }




}

Configurations:

  1. in batch-infra.xml:

    <batch:job-repository id="jobRepository" data-source="dataSource"
                      transaction-manager="transactionManager" />
    
    <!-- connect to database -->
    <bean id="dataSource"
          class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.apache.derby.jdbc.ClientDriver" />
        <property name="url" value="jdbc:derby://localhost:1527/MyDB" />
    

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

    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    

  2. batch-jobs.xml:

    <import resource="classpath:/META-INF/spring/batch/jobs/myJob.xml" />
    
  3. in app-context.xml(after xmlns and all):

    <context:component-scan base-package="sample.peektry" />
    
    <import resource="classpath:/META-INF/spring/batch/batch-infra.xml" />
    
    <import resource="classpath:/META-INF/spring/batch/batch-jobs.xml" />
    

The above configuration is working fine for all the properties except dataSource. I.e. on running, I am getting: IllegalArgumentException DataSource must be provided

Stacktrace:

2014-02-16 14:56:07,195 INFO [org.springframework.context.support.ClassPathXmlApplicationContext] - <Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@c7e553: startup date [Sun Feb 16 14:56:07 IST 2014]; root of context hierarchy>
2014-02-16 14:56:07,291 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - <Loading XML bean definitions from class path resource [META-INF/spring/app-context.xml]>
2014-02-16 14:56:07,625 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - <Loading XML bean definitions from class path resource [META-INF/spring/batch/batch-infra.xml]>
2014-02-16 14:56:10,210 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - <Loading XML bean definitions from class path resource [META-INF/spring/batch/batch-jobs.xml]>
2014-02-16 14:56:12,766 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - <Loading XML bean definitions from class path resource [META-INF/spring/batch/jobs/myJob.xml]>
2014-02-16 14:56:12,872 INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] - <Overriding bean definition for bean 'myJob': replacing [Generic bean: class [org.springframework.batch.core.configuration.xml.SimpleFlowFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] with [Generic bean: class [org.springframework.batch.core.configuration.xml.JobParserJobFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null]>
2014-02-16 14:56:13,115 INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] - <Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1d332b: defining beans [myJdbcReader,myPeekableReader,myPrepSetter,myReader,myRowMapper,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.batch.core.scope.internalStepScope,org.springframework.beans.factory.config.CustomEditorConfigurer,org.springframework.batch.core.configuration.xml.CoreNamespacePostProcessor,jobRepository,dataSource,jobLauncher,transactionManager,step1,myJob]; root of factory hierarchy>
2014-02-16 14:56:13,215 INFO [org.springframework.jdbc.datasource.DriverManagerDataSource] - <Loaded JDBC driver: org.apache.derby.jdbc.ClientDriver>
2014-02-16 14:56:13,304 INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] - <Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1d332b: defining beans [myJdbcReader,myPeekableReader,myPrepSetter,myReader,myRowMapper,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.batch.core.scope.internalStepScope,org.springframework.beans.factory.config.CustomEditorConfigurer,org.springframework.batch.core.configuration.xml.CoreNamespacePostProcessor,jobRepository,dataSource,jobLauncher,transactionManager,step1,myJob]; root of factory hierarchy>
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myJdbcReader' defined in file [C:\Users\user\Documents\workspace-sts-3.3.0.RELEASE\sb-listener-test\target\classes\sample\peektry\MyJdbcReader.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: DataSource must be provided
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1420)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:293)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:290)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:192)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:585)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:895)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:425)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
    at sample.peektry.MyMain.main(MyMain.java:16)
Caused by: java.lang.IllegalArgumentException: DataSource must be provided
    at org.springframework.util.Assert.notNull(Assert.java:112)
    at org.springframework.batch.item.database.AbstractCursorItemReader.afterPropertiesSet(AbstractCursorItemReader.java:150)
    at org.springframework.batch.item.database.JdbcCursorItemReader.afterPropertiesSet(JdbcCursorItemReader.java:107)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1477)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1417)
    ... 12 more

Where I am getting wrong? Is it due to mixing XML configuration and component scan?

Was it helpful?

Solution

The problem is with your dataSource field in your MyJdbcReader class. Actually, you're not overriding the dataSource field of the superclass, simply because it is private (in the superclass). See source code on grepcode.

What you're doing is to declare a new package-private field, which is by no means connected to the dataSource field in the superclass. See inheritance principles in Java for more details.

By looking to the source code (mentioned in the hyperlink above), you can see at line 150 that one expects the dataSource field to be set. But in your case, it's not! Just because you're wiring your DataSource to a field that has nothing to do with the dataSource in the superclass.

What I'd suggest you is to override the setter method (which is public) and move the annotation to the setter. Something like this:

Remove these lines:

@Resource
DataSource dataSource;

and change the setDataSource definition like the following:

@Override
@Resource
public void setDataSource(DataSource dataSource){
    super.setDataSource(dataSource);
}

and remove the getDataSource definition. It will fail, as you won't have a dataSource variable in scope anymore.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top