先说下遇到的问题
No qualifying bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: pgDataSource,DataSource
这个是我在springboot项目中配置双数据源的时候出现的问题,看到这个问题,第一反应就是代码中有某个位置使用的@Autowired注入了DataSource,然而我并未在项目中找到这样的代码。
直到读到下面这部分的异常信息
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: postgresqlDataSource,bpmDataSource
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:136) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:408) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1570) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:220) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:332) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerPostProcessor.postProcessAfterInitialization(DataSourceInitializerPostProcessor.java:62) ~[spring-boot-autoconfigure-1.3.5.RELEASE.jar:1.3.5.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:422) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1583) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
... 40 common frames omitted
才发现是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();
}
这是异常的代码。this.applicationContext.getBean(DataSource.class);在这位置通过类型来获取数据源,然后就出现了上述的异常。
下面介绍的是springboot的注释的方法配置多数据源
@Configuration
public class GlobalDataConfiguration {
@Bean(name = "bpmDataSource")
@Primary
@ConfigurationProperties(prefix = "bpm.datasource")
public DataSource primaryDataSource() {
return new com.alibaba.druid.pool.DruidDataSource();
}
@Bean(name = "postgresqlDataSource")
@ConfigurationProperties(prefix = "workbench.pgsql")
public DataSource secondaryDataSource() {
DataSource dataSource = new com.alibaba.druid.pool.DruidDataSource();
return dataSource;
}
@Bean(name = "mysqlNamedParameterJdbcTemplate")
public NamedParameterJdbcTemplate primaryJdbcTemplate(@Qualifier("bpmDataSource") DataSource dataSource) {
return new NamedParameterJdbcTemplate(dataSource);
}
@Bean(name = "postgresqlNamedParameterJdbcTemplate")
public NamedParameterJdbcTemplate secondaryJdbcTemplate(@Qualifier("postgresqlDataSource") DataSource dataSource) {
return new NamedParameterJdbcTemplate(dataSource);
}
}
bpm.datasource.url=
bpm.datasource.username=
bpm.datasource.password=
workbench.pgsql.url=
workbench.pgsql.username=
workbench.pgsql.password=
切换到这个配置之后上述问题解决了。
if (this.applicationContext.getBeanNamesForType(DataSource.class, false,
false).length > 0) {
this.dataSource = this.applicationContext.getBean(DataSource.class);
}
但是这个代码中的的数据源也是两个,很不理解,继续跟踪代码进行分析。手下判断的length是数据源的总量这个没有问题。
那么问题就发生在了getBean这个方法上了。
下一步进入到DefaultListableBeanFactory这个类
@Override
public T getBean(Class requiredType) throws BeansException {
return getBean(requiredType, (Object[]) null);
}
@Override
public T getBean(Class requiredType, Object... args) throws BeansException {
Assert.notNull(requiredType, "Required type must not be null");
String[] beanNames = getBeanNamesForType(requiredType);
if (beanNames.length > 1) {
ArrayList<String> autowireCandidates = new ArrayList<String>();
for (String beanName : beanNames) {
if (!containsBeanDefinition(beanName) || getBeanDefinition(beanName).isAutowireCandidate()) {
autowireCandidates.add(beanName);
}
}
if (autowireCandidates.size() > 0) {
beanNames = autowireCandidates.toArray(new String[autowireCandidates.size()]);
}
}
if (beanNames.length == 1) {
return getBean(beanNames[0], requiredType, args);
}
else if (beanNames.length > 1) {
Map<String, Object> candidates = new HashMap<String, Object>();
for (String beanName : beanNames) {
candidates.put(beanName, getBean(beanName, requiredType, args));
}
String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
if (primaryCandidate != null) {
return getBean(primaryCandidate, requiredType, args);
}
String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
if (priorityCandidate != null) {
return getBean(priorityCandidate, requiredType, args);
}
throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet());
}
else if (getParentBeanFactory() != null) {
return getParentBeanFactory().getBean(requiredType, args);
}
else {
throw new NoSuchBeanDefinitionException(requiredType);
}
}
通过上面代码我们可以发现当同类型的实现存在多个的时候beanNames.length >1,再往下走进入determinePrimaryCandidate这个方法,我们可以看到
isPrimary(candidateBeanName, beanInstance)
这句验证代码
protected boolean isPrimary(String beanName, Object beanInstance) {
if (containsBeanDefinition(beanName)) {
return getMergedLocalBeanDefinition(beanName).isPrimary();
}
BeanFactory parentFactory = getParentBeanFactory();
return (parentFactory instanceof DefaultListableBeanFactory &&
((DefaultListableBeanFactory) parentFactory).isPrimary(beanName, beanInstance));
}
在这个位置我们发现了问题的根本原因了,我们用springboot配置的时候使用了@Primary的注解,然后spring会默认的加载有这个注解的对象。当存在多个对象而且还无法区分默认加载那一个对象的时候就会出现本文中的问题了。当然了,本着可配置一定可编程的原则,反之亦是通用的。我们可以在数据源的配置xml中加上
primary="true"
这个问题就很开心的解决掉了
这个是最终的多数据库连接的配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
default-autowire="byName">
<bean id="bpmDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close" primary="true" >
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="${datasource.url}" />
<property name="username" value="${datasource.username}" />
<property name="password" value="${datasource.password}" />
<property name="initialSize" value="1" />
<property name="minIdle" value="1" />
<property name="maxActive" value="40" />
<property name="maxWait" value="60000" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="25200000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
bean>
<bean id="mysqlTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref local="bpmDataSource" />
property>
bean>
<bean id="mysqlNamedParameterJdbcTemplate"
class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg index="0" ref="bpmDataSource" />
bean>
<context:component-scan base-package="com.demo.sop.service" />
<bean id="postgresqlDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="org.postgresql.Driver" />
<property name="url" value="${pgsql.url}" />
<property name="username" value="${pgsql.username}" />
<property name="password" value="${pgsql.password}" />
<property name="initialSize" value="1" />
<property name="minIdle" value="1" />
<property name="maxActive" value="40" />
<property name="maxWait" value="60000" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="25200000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="filters" value="stat,log4j" />
bean>
<bean id="postgresqlTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref local="postgresqlDataSource" />
property>
bean>
<tx:annotation-driven transaction-manager="postgresqlTransactionManager" />
<bean id="postgresqlNamedParameterJdbcTemplate"
class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg index="0" ref="postgresqlDataSource" />
bean>
beans>