首先,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. 洛谷 P1027 【Car的旅行路线】

    题目描述 又到暑假了,住在城市A的Car想和朋友一起去城市B旅游.她知道每个城市都有四个飞机场,分别位于一个矩形的四个顶点上,同一个城市中两个机场之间有一条笔直的高速铁路,第i个城市中高速铁路的单位里 ...

  2. windows系统如何查看端口被占用、杀进程

    1.首先启动windows的命令窗口 2.进入windows命令窗口之后,输入netstat -ano,就可以看到系统当前所有的端口使用情况 3.通过命令查找某一特定端口,在命令窗口中输入命令中输入n ...

  3. IntelliJ IDEA 和 Pycharm 破解

    关键网址:http://idea.lanyus.com/ 步骤: 1. 在http://idea.lanyus.com/上下载:JetbrainsCrack-2.9-release-enc.jar . ...

  4. CentOS6.9切换root用户su root输入正确密码后一直提示Incorrect password,如何解决?

    su是切换用户命令,su root时,输入正确的root命令,却提示Incorrect password,当前用户为普通用户,遇到此问题该如何解决呢? 如果设置了wheel组,使用su root命令是 ...

  5. selenium “could not be scrolled into view”

    学习selenium对话框处理出现错误 a.py内容: from selenium import webdriver import timedriver = webdriver.Firefox()dr ...

  6. 《Professional JavaScript for Web Developers》day01

    <professional JavaScript for Web Developers>day01 1.JavaScript简介 1.1JavaScript简史:略 1.2JavaScri ...

  7. windows编程命名规则

    转自:http://blog.sina.com.cn/s/blog_52cbfc3f0100fdy6.html 匈牙利命名法是一种编程时的命名规范.基本原则是:变量名=属性+类型+对象描述.其中每一对 ...

  8. Flex4之皮肤定制

    Flex4之皮肤定制[Skin类和Skin类]          博客分类: RIA-Flex4专栏 FlexAdobeUPFlashUI 第一.关于spark.skin.SparkSkin类的 1. ...

  9. JDK8下Object类源码理解

    JDK8中Object类提供的方法: package java.lang; /** * Class {@code Object} is the root of the class hierarchy. ...

  10. PAT 1152 Google Recruitment

    1152 Google Recruitment (20 分)   In July 2004, Google posted on a giant billboard along Highway 101 ...