首先,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. 2019 年 SpreadJS产品路线图(Roadmap)

    前言 | 问题背景 SpreadJS已经发展成为市场上最好的纯前端JavaScript电子表格之一,但对于用户的需要和产品功能的改善,我们才刚刚开始! 在过去的一年中,我们发布了两个Service P ...

  2. Git仓库删除大文件

    Git仓库删除大文件 背景 当用Git久了,难免会手误或临时添加一些大文件到仓库中,即使以后添加进了.gitignore,甚至做了git rm,但是Git为了保证版本可回退,history pack里 ...

  3. Unity外包团队:关于手机unity游戏开发的技术选型

    技术选型 Unity引擎内置了多人联机的解决方案,涵盖了从最底层的网络数据传输,到不同玩家之间的消息发送,再到游戏大厅这样的高级功能.考虑到Unity官方提供的云服务(Internet Service ...

  4. Spark操作dataFrame进行写入mysql,自定义sql的方式

    业务场景: 现在项目中需要通过对spark对原始数据进行计算,然后将计算结果写入到mysql中,但是在写入的时候有个限制: 1.mysql中的目标表事先已经存在,并且当中存在主键,自增长的键id 2. ...

  5. [转]pycharm 2016.2注册码

    在网上找了好多都不行,最后还是这个好使.在网上搜索了半天挨个试过来最终找到了License server的方法,由此分享给大家.这个也完全没有使用时间的限制~ 直接打开pycharm,选License ...

  6. hashlib 和 hmac 算法的区别

    -----md5 = hashlib.md5() md5.update(password+salt) md5.hexdigest() ----- h = hmac.new(key,password,d ...

  7. WebAPI调用笔记

    前言 即时通信项目中初次调用OA接口遇到了一些问题,因为本人从业后几乎一直做CS端项目,一个简单的WebAPI调用居然浪费了不少时间,特此记录. 接口描述 首先说明一下,基于Http协议的Get.Po ...

  8. hdu多校第4场E. Matrix from Arrays HDU 二维前缀和

    Problem E. Matrix from Arrays Time Limit: / MS (Java/Others) Memory Limit: / K (Java/Others) Total S ...

  9. jsp页面怎么引入js

    人老了,容易忘记事情,再此记录 这里需要注意的是js文件夹要直接放在WebRoot或者WebContent文件夹下面,不要放在WEB-INF下面.因为TomCat对WEB-INF这个文件夹下面的资源是 ...

  10. java通过HttpClient方式和HttpURLConnection方式调用WebService接口

    1.引入maven依赖: <dependency> <groupId>org.apache.httpcomponents</groupId> <artifac ...