spring和springboot 多数据源配置和问题解决

15 篇文章 0 订阅
5 篇文章 0 订阅

先说下遇到的问题

 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> T getBean(Class<T> requiredType) throws BeansException {
        return getBean(requiredType, (Object[]) null);
    }

    @Override
    public <T> T getBean(Class<T> 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" 

这个问题就很开心的解决掉了

这个是最终的多数据库连接的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<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" />

    <!-- pgsql -->
    <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" />
        <!-- 配置监控统计拦截的filters -->
        <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>
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值