Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Batch with two different datasource issue

I have a simple Spring Batch application that's pulling records from a database and just printing the rows to the screen. Just a simple POC application.

The application works just fine with Spring Boot 1.2.1.RELEASE but when I updated to 1.2.3.RELEASE I get an error message about "No qualifying bean of type [javax.sql.DataSource] is defined"

I'm not sure if this is a Spring Boot issue or Spring Batch issue.

Is there way to define the datasource explicitly for the Spring Batch repository?

Full stack trace.

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private java.util.Collection org.springframework.batch.core.configuration.annotation.AbstractBatchConfiguration.dataSources; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [demo/BatchConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSourceInitializer': Invocation of init method failed; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: dataSource,consumerAppointmentDataSource
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:334)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1210)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:755)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:757)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:686)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:320)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:957)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:946)
    at demo.Application.main(Application.java:13)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private java.util.Collection org.springframework.batch.core.configuration.annotation.AbstractBatchConfiguration.dataSources; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [demo/BatchConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSourceInitializer': Invocation of init method failed; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: dataSource,consumerAppointmentDataSource
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:561)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331)
    ... 15 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [demo/BatchConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSourceInitializer': Invocation of init method failed; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: dataSource,consumerAppointmentDataSource
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:547)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1120)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:996)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:533)
    ... 17 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSourceInitializer': Invocation of init method failed; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: dataSource,consumerAppointmentDataSource
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:136)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:408)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1566)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:217)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:350)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:331)
    at org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerPostProcessor.postProcessAfterInitialization(DataSourceInitializerPostProcessor.java:62)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:422)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1579)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539)
    ... 26 common frames omitted
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: dataSource,consumerAppointmentDataSource
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:365)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:331)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:968)
    at org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer.init(DataSourceInitializer.java:67)
    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.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:349)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:300)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:133)
    ... 40 common frames omitted

Sample Code

@Configuration
@EnableBatchProcessing
public class BatchConfiguration {

    @Bean   
    @ConfigurationProperties(prefix = "datasource")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "datasource.consumerappointment")
    public DataSource consumerAppointmentDataSource() {
        return DataSourceBuilder.create().build();
    }



    @Bean
    public Job importUserJob(JobBuilderFactory jobs, Step s1) {
        return jobs.get("importUserJob")
                .incrementer(new RunIdIncrementer())
                .flow(s1)
                .end()
                .build();
    }

    @Bean
    public Step step1(StepBuilderFactory stepBuilderFactory,
                      ItemReader<AppointmentVerification> reader,
                      ItemProcessor<AppointmentVerification, AppointmentVerification> processor) {
        return stepBuilderFactory.get("step1")
                .<AppointmentVerification, AppointmentVerification> chunk(10)               
                .reader(reader)
                .processor(processor)
                .build();
    }

    @Bean
    public ItemProcessor<AppointmentVerification, AppointmentVerification> processorAppointmentVerification() {
        return new AppointmentVerificationItemProcessor();
    }

    @Bean
    public ItemReader<AppointmentVerification> appointmentVerificationReader(DataSource consumerAppointmentDataSource) {
        JdbcCursorItemReader<AppointmentVerification> reader = new JdbcCursorItemReader<AppointmentVerification>();
        String sql = "select * from test";
        reader.setSql(sql);
        reader.setDataSource(consumerAppointmentDataSource);
        reader.setRowMapper(rowMapper());
        return reader;
    }

    private RowMapper<AppointmentVerification> rowMapper() {

        return new RowMapper<AppointmentVerification>() {

            @Override
            public AppointmentVerification mapRow(ResultSet rs, int i)
                    throws SQLException {
                AppointmentVerification appointmentVerification = new AppointmentVerification();
                appointmentVerification.setEmail(rs.getString("CNSM_EML_ADR"));
                return appointmentVerification;
            }
        };
    }

}

Edited Fix: Updated the main datasource to primary Created a BatchConfigurer bean. Add a @Qualifier to the itemreader

@Configuration
@EnableBatchProcessing
public class BatchConfiguration {

    @Primary
    @Bean
    @ConfigurationProperties(prefix = "datasource.batch")
    public DataSource batchDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "datasource.consumerappointment")
    public DataSource consumerAppointmentDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public BatchConfigurer configurer(DataSource batchDataSource){
        return new DefaultBatchConfigurer(batchDataSource);
    }

    @Bean
    public Job importUserJob(JobBuilderFactory jobs, Step s1) {
        return jobs.get("importUserJob")
                .incrementer(new RunIdIncrementer())
                .flow(s1)
                .end()
                .build();
    }

    @Bean
    public Step step1(StepBuilderFactory stepBuilderFactory,
                      ItemReader<AppointmentVerification> reader,
                      ItemWriter<AppointmentVerification> messageWriter) {
        return stepBuilderFactory.get("step1")
                .<AppointmentVerification, AppointmentVerification> chunk(10)
                .reader(reader)
                .writer(messageWriter)
                .build();
    }

    @Bean
    public ItemReader<AppointmentVerification> appointmentVerificationReader(@Qualifier(value = "consumerAppointmentDataSource") DataSource consumerAppointmentDataSource) {
        JdbcCursorItemReader<AppointmentVerification> reader = new JdbcCursorItemReader<AppointmentVerification>();
        String sql = "select * from test";
        reader.setSql(sql);
        reader.setDataSource(consumerAppointmentDataSource);
        reader.setRowMapper(rowMapper());
        return reader;
    }

    private RowMapper<AppointmentVerification> rowMapper() {

        return new RowMapper<AppointmentVerification>() {
            @Override
            public AppointmentVerification mapRow(ResultSet rs, int i) throws SQLException {
                AppointmentVerification appointmentVerification = new AppointmentVerification();
                appointmentVerification.setEmail(rs.getString("CNSM_EML_ADR"));
                                    return appointmentVerification;
            }
        };
    }
like image 488
zachariahyoung Avatar asked May 29 '15 19:05

zachariahyoung


People also ask

How do I use multiple datasource in Spring Batch?

With the xml one you have to be explicit in which datasource Spring Batch uses. If you don't declare it explicitly with Java based configuration it will try to detect the datasource to work, which will only work in case a single datasource is detected. YOu could try annotating the one to use for Batch with @Primary .

Can we use multiple datasource in spring boot?

Spring Boot provides first-class support to the Spring JPA that makes it easy to access the database with little boilerplate code by using Spring Repositories feature. Spring Boot does not provide an out of the box solution in case our application needs multiple DataSources (e.g. multi-tenant system).


1 Answers

Mark one of your beans as primary

@Primary
@Bean
@ConfigurationProperties(prefix = "datasource.consumerappointment")
public DataSource consumerAppointmentDataSource() {
    return DataSourceBuilder.create().build();
}

then create a BatchConfigurer with it

@Bean
BatchConfigurer configurer(DataSource dataSource){
  return new DefaultBatchConfigurer(dataSource);
}
like image 52
jst Avatar answered Sep 21 '22 13:09

jst