千家信息网

如何解决springboot mybatis调用多个数据源引发的错误问题

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,这篇文章给大家分享的是有关如何解决springboot mybatis调用多个数据源引发的错误问题的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。springboot myb
千家信息网最后更新 2026年11月01日如何解决springboot mybatis调用多个数据源引发的错误问题

这篇文章给大家分享的是有关如何解决springboot mybatis调用多个数据源引发的错误问题的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

springboot mybatis调用多个数据源错误

报错

'org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker': Invocation of init method failed; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.sql.DataSource' available: more than one 'primary' bean found among candidates: [mssqlDataSource, postgreDataSource]

从后往前复制的,加粗的是重点。

因为有多个数据源使用同一个mapper接口,但是都用@Primary,则会引起此错误。

如图所示:

从上面两图可以看出都用了同一个mapper接口,都添加了@Primary。

解决方法

解决方法有两种,一种是把其中一个数据源去掉@Primary,动态调用数据源,就是需要代码切换使用的数据源。

如果要同时使用两个数据源,那就用不同的mapper,相当于postgre用postgre部分的mapper,sqlserver用sqlserver部分的mapper,大家互不干扰,就算@primary也没事

如图所示,我将postgre的MapperScan改了

springboot-mybatis多数据源及踩坑

springboot项目结构如下

springboot配置文件内容如下

动态数据源的配置类如下

(必须保证能被ComponentScan扫描到):

package com.letzgo.config;import com.alibaba.druid.pool.DruidDataSource;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;import org.mybatis.spring.SqlSessionTemplate;import org.mybatis.spring.annotation.MapperScan;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import javax.sql.DataSource;/** * @author allen * @date 2019-01-10 15:08 */public class DynamicDatasourceConfig {    @Configuration    @MapperScan(basePackages = "com.letzgo.dao.master")    public static class Master {        @Primary        @Bean("masterDataSource")        @Qualifier("masterDataSource")        @ConfigurationProperties(prefix = "spring.datasource.master")        public DataSource dataSource() {            return new DruidDataSource();        }        @Primary        @Bean("masterSqlSessionFactory")        @Qualifier("masterSqlSessionFactory")        public SqlSessionFactory sqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {            SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();            factoryBean.setDataSource(dataSource);            factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/master/*.xml"));            return factoryBean.getObject();        }        @Primary        @Bean("masterTransactionManager")        @Qualifier("masterTransactionManager")        public DataSourceTransactionManager transactionManager(@Qualifier("masterDataSource") DataSource dataSource) {            return new DataSourceTransactionManager(dataSource);        }        @Primary        @Bean("masterSqlSessionTemplate")        @Qualifier("masterSqlSessionTemplate")        public SqlSessionTemplate sqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {            return new SqlSessionTemplate(sqlSessionFactory);        }    }    @Configuration    @MapperScan(basePackages = "com.letzgo.dao.slave")    public static class Slave {        @Bean("slaveDataSource")        @Qualifier("slaveDataSource")        @ConfigurationProperties(prefix = "spring.datasource.slave")        public DataSource dataSource() {            return new DruidDataSource();        }        @Bean("slaveSqlSessionFactory")        @Qualifier("slaveSqlSessionFactory")        public SqlSessionFactory sqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {            SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();            factoryBean.setDataSource(dataSource);            factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/slave/*.xml"));            return factoryBean.getObject();        }        @Bean("slaveTransactionManager")        @Qualifier("slaveTransactionManager")        public DataSourceTransactionManager transactionManager(@Qualifier("slaveDataSource") DataSource dataSource) {            return new DataSourceTransactionManager(dataSource);        }        @Bean("slaveSqlSessionTemplate")        @Qualifier("slaveSqlSessionTemplate")        public SqlSessionTemplate sqlSessionTemplate(@Qualifier("slaveSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {            return new SqlSessionTemplate(sqlSessionFactory);        }    }}

完成基本配置之后,分别在master和slave中写一个数据库访问操作,再开放两个简单的接口,分别触发master和slave的数据看访问操作。

至此没项目基本结构搭建已完成,启动项目,进行测试。

我们会发现这样master的数据库访问是能正常访问的,但是slave的数据库操作是不行的,报错信息如下:

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found):***

对于这样错误,起初企图通过百度解决,大部分都是说xml文件的命名空间和dao接口全名不对应或者说是接口方法和xml中的方法不对应等等解决方法,

本人检查了自己的代码多遍重启多遍均无法解决,并不是说这些方法不对,但是本案例的问题却不是这些问题导致的。最后无奈,只能硬着头皮去看源码,最后发现了问题所在。

debug源码调试到最后,发现不论是执行mater还是slave的数据库操作,使用了相同的SqlSession,同一个!!!这个肯定是有问题的。

继续看源码进行查,看SqlSession的注入过程。

我们知道mybatis只要写接口不用写实现类(应该是3.0之后的版本),实际上是使用了代理,每个dao接口,在spring容器中其实是对应一个MapperFactoryBean(不懂FactoryBean的可以去多看看spring的一些核心接口,要想看懂spring源码必须要知道的)。

当从容器中获取bean的时候,MapperFactoryBean的getObject方法就会根据SqlSession实例生产一个MapperProxy对象的代理类。

问题的关键就在于MapperFactoryBean,他继承了SqlSessionDaoSupport类,他有一个属性,就是SqlSession,而且刚才所说的创建代理类所依赖的SqlSession实例就是这个。那我们看这个SqlSession实例是什么时候注入的就可以了,就能找到为什么注入了同一个对象了。

找spring注入的地方,spring注入的方式个人目前知道的有注解处理器如@Autowired的注解处理器AutowiredAnnotationBeanPostProcessor等类似的BeanPostProcessor接口的实现类,还有一种就是在BeanDefinition中定义器属性的注入方式,在bean的定义阶段就决定了的,前者如果不知道的可以看看,在此不做赘述,后者的处理过程源码如下(只截取核心部分,感兴趣的可以自己看一下处理过程,调用链比较深,贴代码会比较多,看着眼花缭乱):

debug到dao接口类的的BeanDefinition(上文已说过其实是MapperFactoryBean),发现他的autowiremode是2,参照源码

即可发现为按照类型自动装配

最关键的来了

debug的时候发现,master的dao接口执行到this.autowireByType(beanName, mbd, bw, newPvs)方法中,给MapperFactoryBean中SqlSession属性注入的实例是masterSqlSessionTemplate对象,

slave的dao接口执行该方法时注入的也是masterSqlSessionTemplate对象,按类型注入,spring容器中找到一个即注入(此时slaveSqlSessionTemplate也在容器中,为什么按类型注入找到了masterSqlSessionTemplate却没报错,应该是@Primary的作用)

至此,问题产生的原因已基本找到,那该如何解决呢?BeanDefinition为什么会定义成autowiremode=2呢,只能找@MapperScan看了,看这个注解的处理源码,最后找到ClassPathMapperScanner以下方法:

private void processBeanDefinitions(Set beanDefinitions) {        Iterator var3 = beanDefinitions.iterator();        while(var3.hasNext()) {            BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();            GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();            if (this.logger.isDebugEnabled()) {                this.logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface");            }            definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());            definition.setBeanClass(this.mapperFactoryBean.getClass());            definition.getPropertyValues().add("addToConfig", this.addToConfig);            boolean explicitFactoryUsed = false;            if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {                definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));                explicitFactoryUsed = true;            } else if (this.sqlSessionFactory != null) {                definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);                explicitFactoryUsed = true;            }            if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {                if (explicitFactoryUsed) {                    this.logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");                }                definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));                explicitFactoryUsed = true;            } else if (this.sqlSessionTemplate != null) {                if (explicitFactoryUsed) {                    this.logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");                }                definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);                explicitFactoryUsed = true;            }            if (!explicitFactoryUsed) {                if (this.logger.isDebugEnabled()) {                    this.logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");                }                definition.setAutowireMode(2);            }        }    }

44行是关键,但是有个条件,这个条件成立的原因就是@MapperScan注解没有指定过sqlSessionTemplateRef或者sqlSessionFactoryRef,正因为没有指定特定的sqlSessionTemplate或者sqlSessionFactory,mybatis默认采用按类型自动装配的方式进行注入。

至此,问题解决方案已出:

代码中的两个@MapperScan用法分别改为:

@MapperScan(basePackages = "com.letzgo.dao.master", sqlSessionFactoryRef = "masterSqlSessionFactory", sqlSessionTemplateRef = "masterSqlSessionTemplate")  @MapperScan(basePackages = "com.letzgo.dao.slave", sqlSessionFactoryRef = "slaveSqlSessionFactory", sqlSessionTemplateRef = "slaveSqlSessionTemplate")

重启进行测试,问题解决。

感谢各位的阅读!关于"如何解决springboot mybatis调用多个数据源引发的错误问题"这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

数据 接口 数据源 问题 方法 源码 错误 就是 处理 多个 代码 实例 容器 对象 数据库 注解 类型 两个 关键 内容 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网科技图片 广州紫鲸互联网科技有限公司 广州百单互联网科技有限公司 互联网科技公司发展 有马互联网科技 礼当家互联网科技 上游互联网科技 蓝色互联网科技 北京华百购互联网科技有限公司 厦门比较好的科技互联网公司 昭通互联网科技选哪家 东莞新华互联网科技学校 玉溪新华互联网科技好不好 群盈汇互联网科技有限公司 互联网时代的科技公司 每天都有哪些好的互联网科技 富国互联网金融科技混合基金 广州互联网高科技取餐柜 福建互联网展示科技馆设计 世界互联网科技成果奖 科技互联网公司装修 互联网的科技创新 互联网科技集团 齐家互联网科技 深圳卡尔迅互联网科技有限公司怎么样 受益于互联网科技的高速发展 徐州市互联网科技有限公司 互联网企业科技类企业如何做账 从那以后互联网科技有限公司 互联网界的黑科技
0