首先,springboot项目结构如下

springboot配置文件内容如下

动态数据源的配置类如下(必须保证能被ComponentScan扫描到):

 1 package com.letzgo.config;
2
3 import com.alibaba.druid.pool.DruidDataSource;
4 import org.apache.ibatis.session.SqlSessionFactory;
5 import org.mybatis.spring.SqlSessionFactoryBean;
6 import org.mybatis.spring.SqlSessionTemplate;
7 import org.mybatis.spring.annotation.MapperScan;
8 import org.springframework.beans.factory.annotation.Qualifier;
9 import org.springframework.boot.context.properties.ConfigurationProperties;
10 import org.springframework.context.annotation.Bean;
11 import org.springframework.context.annotation.Configuration;
12 import org.springframework.context.annotation.Primary;
13 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
14 import org.springframework.jdbc.datasource.DataSourceTransactionManager;
15
16 import javax.sql.DataSource;
17
18 /**
19 * @author allen
20 * @date 2019-01-10 15:08
21 */
22 public class DynamicDatasourceConfig {
23
24 @Configuration
25 @MapperScan(basePackages = "com.letzgo.dao.master")
26 public static class Master {
27 @Primary
28 @Bean("masterDataSource")
29 @Qualifier("masterDataSource")
30 @ConfigurationProperties(prefix = "spring.datasource.master")
31 public DataSource dataSource() {
32 return new DruidDataSource();
33 }
34
35 @Primary
36 @Bean("masterSqlSessionFactory")
37 @Qualifier("masterSqlSessionFactory")
38 public SqlSessionFactory sqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {
39 SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
40 factoryBean.setDataSource(dataSource);
41 factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/master/*.xml"));
42 return factoryBean.getObject();
43 }
44
45 @Primary
46 @Bean("masterTransactionManager")
47 @Qualifier("masterTransactionManager")
48 public DataSourceTransactionManager transactionManager(@Qualifier("masterDataSource") DataSource dataSource) {
49 return new DataSourceTransactionManager(dataSource);
50 }
51
52 @Primary
53 @Bean("masterSqlSessionTemplate")
54 @Qualifier("masterSqlSessionTemplate")
55 public SqlSessionTemplate sqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
56 return new SqlSessionTemplate(sqlSessionFactory);
57 }
58
59 }
60
61 @Configuration
62 @MapperScan(basePackages = "com.letzgo.dao.slave")
63 public static class Slave {
64 @Bean("slaveDataSource")
65 @Qualifier("slaveDataSource")
66 @ConfigurationProperties(prefix = "spring.datasource.slave")
67 public DataSource dataSource() {
68 return new DruidDataSource();
69 }
70
71 @Bean("slaveSqlSessionFactory")
72 @Qualifier("slaveSqlSessionFactory")
73 public SqlSessionFactory sqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {
74 SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
75 factoryBean.setDataSource(dataSource);
76 factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/slave/*.xml"));
77 return factoryBean.getObject();
78 }
79
80 @Bean("slaveTransactionManager")
81 @Qualifier("slaveTransactionManager")
82 public DataSourceTransactionManager transactionManager(@Qualifier("slaveDataSource") DataSource dataSource) {
83 return new DataSourceTransactionManager(dataSource);
84 }
85
86 @Bean("slaveSqlSessionTemplate")
87 @Qualifier("slaveSqlSessionTemplate")
88 public SqlSessionTemplate sqlSessionTemplate(@Qualifier("slaveSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
89 return new SqlSessionTemplate(sqlSessionFactory);
90 }
91 }
92
93 }

完成基本配置之后,分别在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<BeanDefinitionHolder> 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")

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

PS:

还是对各种注解使用方法不了解(或者说对框架的源码不了解),导致搞了这么久的问题,还好最后查到了,记录于此,给自己加深印象,也希望解决方案能帮到部分同行。以后还是要多看源码,哈哈哈。

springboot-mybatis多数据源以及踩坑之旅的更多相关文章

  1. spring-boot (四) springboot+mybatis多数据源最简解决方案

    学习文章来自:http://www.ityouknow.com/spring-boot.html 配置文件 pom包就不贴了比较简单该依赖的就依赖,主要是数据库这边的配置: mybatis.confi ...

  2. springboot + mybatis + 多数据源

    此文已由作者赵计刚薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验 在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源. 代码结构: 简要原理: 1) ...

  3. 我的微信小程序入门踩坑之旅

    前言 更好的阅读体验请:我的微信小程序入门踩坑之旅 小程序出来也有一段日子了,刚出来时也留意了一下.不过赶上生病,加上公司里也有别的事,主要是自己犯懒,就一直没做.这星期一,赶紧趁着这股热乎劲,也不是 ...

  4. vue+ vue-router + webpack 踩坑之旅

    说是踩坑之旅 其实是最近在思考一些问题 然后想实现方案的时候,就慢慢的查到这些方案   老司机可以忽略下面的内容了 1)起因  考虑到数据分离的问题  因为server是express搭的   自然少 ...

  5. 微信小程序之mpvue+iview踩坑之旅

    因为之前参照微信的原生的文档写过一些小程序的demo,写的过程比较繁琐,后来出了美团的mpvue,可以直接使用vue开发,其他的不作对比,这篇文章记录一下踩坑之旅. 参照mpvue http://mp ...

  6. vue踩坑之旅 -- computed watch

    vue踩坑之旅 -- computed watch 经常在使用vue初始化组件时,会报一些莫名其妙的错误,或者,数据明明有数据,确还是拿不到,这是多么痛苦而又令人忍不住抓耳挠腮,捶胸顿足啊 技术点 v ...

  7. Python踩坑之旅其一杀不死的Shell子进程

    目录 1.1 踩坑案例 1.2 填坑解法 1.3 坑位分析 1.4 坑后扩展 1.4.1 扩展知识 1.4.1 技术关键字 1.5 填坑总结 1.1 踩坑案例 踩坑的程序是个常驻的Agent类管理进程 ...

  8. Python 踩坑之旅进程篇其三pgid是个什么鬼 (子进程\子孙进程无法kill 退出的解法)

    目录 1.1 踩坑案例 1.2 填坑解法 1.3 坑位分析 1.4.1 技术关键字 下期坑位预告 代码示例支持 平台: Centos 6.3 Python: 2.7.14 Github: https: ...

  9. [代码修订版] Python 踩坑之旅 [进程篇其四] 踩透 uid euid suid gid egid sgid的坑坑洼洼

    目录 1.1 踩坑案例 1.2 填坑解法 1.3 坑位分析 1.4 技术关键字 1.5 坑后思考 下期坑位预告 代码示例支持 平台: Centos 6.3 Python: 2.7.14 代码示例: 公 ...

随机推荐

  1. Vue-admin工作整理(十三):Vuex-严格模式

    严格模式:开发过程中对规范的要求,定义方式为在store实例初始化的时候将strict设置为true,这样的话就是开启了严格模式.在这种情况下,如果要直接修改state里面的值,那就会报问题.那么也可 ...

  2. vlog.hpp

    //vov #ifndef VLOG_HPP #define VLOG_HPP #include <sys/time.h> #include <unistd.h> #ifnde ...

  3. tensorboard

    在控制台输入: C:\Users\sunli\Documents\name\src>tensorboard --logdir=./w

  4. 杂记-python

    1.在cmd输入python -V显示当前python的版本信息,一定是大写的V 2.输入python,进入python解释器里面 3.在解释器中,输入exit()或者quit()会退出,一定要加括号 ...

  5. Go语言学习之15 商品秒杀开发与接入层实现

    outline 1. 秒杀抢购接入层实现2. 秒杀逻辑层实现 秒杀接入层核心功能 秒杀逻辑层核心功能 SecKill接口 /seckill?product=20&source=android& ...

  6. Lab 10-3

    This lab includes a driver and an executable. You can run the executable from anywhere, but in order ...

  7. CNN 分割

    测试的是Cifar10数据集,采用VGG模型的网络参数是[32, 'M', 64, 'M', 128, 128, 'M', 256, 256, 'M', 256, 256],准确度大概在90.6左右 ...

  8. CF923E Perpetual Subtraction

    生成函数好题! 搬一手铃悬的题解(侵删) 现在只需要考虑怎么求出g和逆变换即可,其实也就是对函数F(x)求F(x+1)和F(x-1). 直接二项式定理展开发现是个卷积的形式,大力NTT即可. #inc ...

  9. 现代 PHP 新特性 —— 内置的 HTTP 服务器 (转)

    转自 https://laravelacademy.org/post/4422.html 从 PHP 5.4.0 起,PHP内置了Web服务器,这对于认为需要Apache或Nginx才能预览PHP应用 ...

  10. Python面向对象编程 -- 类和实例、访问限制

    面向对象编程 Object Oriented Programming,简称OOP,是一种程序设计思想.OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数. 面向过程的程序设计把计算机程 ...