I used spring boot + jdbctemplate and I have to use multi datasource, e.g.
@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 start my application, it failed and have below error info
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 have used @Qualifier
to identify the bean , e.g.
@Bean(name = "fooJdbcTemplate")
public JdbcTemplate fooJdbcTemplate(@Qualifier("fooDb") DataSource ds)
Why doesn't @Qualifier
work here?
So I've done some debugging and found something which might explain what's happening. At this point I'm not sure if it's a bug (could be this one), but I have not been able to find any other documentation to clarify this either.
For reference this is spring-boot 1.5.4.
I started from the log, you can find below an excerpt, more specifically the line regarding 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 initialising the data sources, spring-boot tries to initialise the DB as well, feature which is enabled by default according to the docs:
Spring JDBC has a
DataSource
initializer feature. Spring Boot enables it by default and loads SQL from the standard locationsschema.sql
anddata.sql
(in the root of the classpath).
This takes place in the @PostConstruct
section of 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 get the DataSource
to execute the DB initialisation using the class this.dataSource = this.applicationContext.getBean(DataSource.class);
and since there are 3 instances and no primary, it fails as per the expected behaviour of getBean(class)
<T> T getBean(Class<T> requiredType) throws BeansException
Return the bean instance that uniquely matches the given object type, if any.
This method goes into ListableBeanFactory by-type lookup territory but may also be translated into a conventional by-name lookup based on the name of the given type. For more extensive retrieval operations across sets of beans, use ListableBeanFactory and/or BeanFactoryUtils.Parameters:
requiredType - type the bean must match; can be an interface or superclass. null is disallowed.Returns:
an instance of the single bean matching the required typeThrows:
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, bottom line, this happens before even trying to autowire your @Qualifier("fooDb")
bean in the method, and I believe you have at lease these 2 choices, and in both cases your @Qualifier
will be taken into account at the time when your JdbcTemplate
is created:
@Primary
to indicate which DataSource
could be used for the taskspring.datasource.initialize=false
in your application.properties
(see here a list of common properties that can be configured)If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With