Why @Qualifier doesn't work

I was using spring boot + jdbctemplate and I need to use multi datasource eg.

@Configuration
public class MultiDBConfig {

    @Bean(name = "fooDb")
    @ConfigurationProperties(prefix = "foo.datasource")
    public DataSource fooDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "fooJdbcTemplate")
    public JdbcTemplate fooJdbcTemplate(@Qualifier("fooDb") DataSource ds) {
        return new JdbcTemplate(ds);
    }

    @Bean(name = "barDb")
    @ConfigurationProperties(prefix = "bar.datasource")
    public DataSource barDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "barJdbcTemplate")
    public JdbcTemplate barJdbcTemplate(@Qualifier("barDb") DataSource ds) {
        return new JdbcTemplate(ds);
    }

}

      

when starting my application it doesn't work and has below error information

Parameter 0 of method fooJdbcTemplate in com.example.multidatasourcedemo.MultiDBConfig required a single bean, but 3 were found:
    - fooDb: defined by method 'fooDataSource' in class path resource [com/example/multidatasourcedemo/MultiDBConfig.class]
    - barDb: defined by method 'barDataSource' in class path resource [com/example/multidatasourcedemo/MultiDBConfig.class]
    - testDb: defined by method 'testDataSource' in class path resource [com/example/multidatasourcedemo/MultiDBConfig.class]


Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

      

But I obviously used @Qualifier

bean for identification, like

@Bean(name = "fooJdbcTemplate")
public JdbcTemplate fooJdbcTemplate(@Qualifier("fooDb") DataSource ds)

      

Why doesn't it work here @Qualifier

?

+3


source to share


2 answers


So I did some debugging and found something that could explain what is going on. At the moment I'm not sure if this is a bug (maybe this one ), but I could not find any other documentation to clarify this.

For reference this is spring-boot 1.5.4.


I started with a log, you can find below excerpt, more precisely the line DataSourceInitializer.init

(below with ==>

at the beginning):

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.sql.DataSource' available: expected single matching bean but found 3: fooDb,barDb,testDb
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1041) ~[spring-beans-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:345) ~[spring-beans-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:340) ~[spring-beans-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1090) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
==> at org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer.init(DataSourceInitializer.java:77) ~[spring-boot-autoconfigure-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_45]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_45]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_45]
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_45]
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:366) ~[spring-beans-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:311) ~[spring-beans-4.3.9.RELEASE
    ...

      

What happens is, when initializing data sources, spring-boot tries to initialize the DB, and the function that is enabled by default according to the docs :

Spring JDBC has an init feature DataSource

. Spring Boot allows by default and loads SQL from standard locations schema.sql

and data.sql

(at the root of the classpath).



This happens in the section @PostConstruct

org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer

:

@PostConstruct
public void init() {
    if (!this.properties.isInitialize()) {
        logger.debug("Initialization disabled (not running DDL scripts)");
        return;
    }
    if (this.applicationContext.getBeanNamesForType(DataSource.class, false, false).length > 0) {
        ==> this.dataSource = this.applicationContext.getBean(DataSource.class);
    }
    if (this.dataSource == null) {
        logger.debug("No DataSource found so not initializing");
        return;
    }
    runSchemaScripts();
}

      

As you can see, it tries to force the DataSource

DB initialization to be done with the class this.dataSource = this.applicationContext.getBean(DataSource.class);

, and since it has three instances and no primary data, it fails as expectedgetBean(class)

<T> T getBean (class <T> requiredType) throws BeansException
Return a bean instance that uniquely matches the given object type, if any.
This method scopes a ListableBeanFactory type search, but can also be scoped to a regular name search based on the name of the given type. For more detailed lookups in sets of beans, use ListableBeanFactory and / or BeanFactoryUtils.

Parameters:
requiredType - enter the bean must match; can be interface or superclass. null is not allowed.

Returns:
a single bean instance corresponding to the required type

Throws :
NoSuchBeanDefinitionException - if no bean of the given type was found
==> NoUniqueBeanDefinitionException - if more than one bean of the given type was found
BeansException - if the bean could not be created


So on the bottom line, this happens even if you are trying to autoincrement your @Qualifier("fooDb")

bean in a method, and I believe you have these 2 options on lease, and in both cases yours @Qualifier

will be taken into account at creation time JdbcTemplate

:

  • if you need to execute some scripts to initialize your db then use @Primary

    to specify which one DataSource

    can be used for the task
  • Otherwise, you can disable this implicit function by adding spring.datasource.initialize=false

    to yours application.properties

    (see here for a list of common properties that can be configured)
+4


source


This can be caused by several different things. In my case, I had the following situation:

  • Two data sources beans are configured in two Java classes, but both are given specific Bean IDs
  • A data source was entered in one place, but is correctly annotated with a classifier
  • SpringBootApplication correctly excluded by DataSourceAutoConfiguration


However, the error turned out: the second class was annotated as SpringBootApplication and this start ... is lost among the logs. So, if everything else looks right: check if some other unexpected SpringBootApplication is running.

0


source







All Articles