需求

在很多具体应用场景中,我们需要用到动态数据源的情况,比如多租户的场景,系统登录时需要根据用户信息切换到用户对应的数据库。又比如业务A要访问A数据库,业务B要访问B数据库等,都可以使用动态数据源方案进行解决。
作为合格的程序员第一时间肯定是去百度,但是呢既然我写了这篇博客那么肯定是没能很好的集成到我项目中,网上写的一篇文章说基于spring的AbstractRoutingDataSource 就可以实现,但是我试了不行,因为我自己项目不是用的jdbcTemplate 也可能是我使用姿势不对,反正没有用上,但是也确实给了我灵感
首先我肯定要知道是通过什么方式去获取的db connect,看了源码心中有数

源码调试

首先mybatis plus也是集成的mybatis,那么最核心的一定是 SqlSessionFactory,
这是一个接口,项目中只有 DefaultSqlSessionFactory 实现了此接口
此时只要项目中打个断点就知道是在 org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);//关键就在于 environment.getDataSource()tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);final Executor executor = configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}

这里的 dataSource 是在 org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration 注入的(别问为啥知道的,问就是百度的)
因为小编项目中用的就是spring默认的连接池,所以是通过如下代码注入

@ConditionalOnClass(HikariDataSource.class)@ConditionalOnMissingBean(DataSource.class)@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true)static class Hikari {@Bean@ConfigurationProperties(prefix = "spring.datasource.hikari")public HikariDataSource dataSource(DataSourceProperties properties) {HikariDataSource dataSource = createDataSource(properties,HikariDataSource.class);if (StringUtils.hasText(properties.getName())) {dataSource.setPoolName(properties.getName());}return dataSource;}}

思路

源码分析发现数据源是通过environment.getDataSource() 方式获取的,第一时间我想到我们替换掉这个 environment,后来发现不行,这个类牵扯的代码太多,不可能重写
又随即想到这是一个接口

public interface DataSource  extends CommonDataSource, Wrapper {Connection getConnection() throws SQLException;Connection getConnection(String username, String password)throws SQLException;
}

就是说不管怎么样,要进行数据库操作一定要调用实现这个接口的方法,那么我们可以替换掉 org.apache.ibatis.mapping.Environment中的 dataSource即可实现我们的目的,上文分析这个类也是注入的,这样修改很方便,此时有两种方案:

  • 自定义一个dataSource,然后声明一个属性,通过租户的key去选择指定的dataSource,再调用 getConnection 方法
  private static ConcurrentMap<String, DataSource> dataSourceConcurrentMap = new ConcurrentHashMap<>();@Overridepublic Connection getConnection() throws SQLException {String dbKey = threadLocal.get();DataSource dataSource = dataSourceConcurrentMap.get(dbKey);return getDataSource().getConnection();}
  • 也是重写一个dataSource,但是重写更彻底,相当于重写一个数据库连接池,也是类似操作,根据租户的信息创建指定的 connect,并且自己维护数据库连接的生命周期

虽然后者实现起来更加优雅和刺激,但是呢我还是选择前者
不管从实现难度还是速度来看都是前者更好(后者我还没这个能力)

代码实现

import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.core.NamedThreadLocal;
import org.springframework.util.StringUtils;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;@Slf4j
public class DyDataSource extends HikariDataSource {// 全局的数据源类型Class<? extends DataSource> type;// 当前线程的租户信息private static ThreadLocal<String> threadLocal = new NamedThreadLocal<>("TARGET_DATA_SOURCE");// 数据源容器private static ConcurrentMap<String, DataSource> dataSourceConcurrentMap = new ConcurrentHashMap<>();public DyDataSource(Class<? extends DataSource> type) {super();this.type = type;log.info("DyDataSource init ...........");}@Overridepublic Connection getConnection() throws SQLException {return getDataSource().getConnection();}@Overridepublic Connection getConnection(String username, String password) throws SQLException {return getDataSource().getConnection(username, password);}public static void setDbKey(String dbKey) {threadLocal.set(dbKey);}// 根据租户信息获取数据源public DataSource getDataSource(){String dbKey = threadLocal.get();if (StringUtils.isEmpty(dbKey)) {throw new RuntimeException("未指定 dbKey");}DataSource dataSource = dataSourceConcurrentMap.get(dbKey);if (dataSource == null) {// 初始化synchronized (dbKey.intern()) {if (dataSource == null) {dataSource = initDataSource(dbKey);}}}if (dataSource == null) {throw new RuntimeException("dataSource is null");}return dataSource;}// 根据租户信息初始化private DataSource initDataSource(String dbKey){DataSourceProperties dataSourceProperties = new DataSourceProperties();dataSourceProperties.setDriverClassName("com.mysql.jdbc.Driver");if (dbKey.equals("inner")) {dataSourceProperties.setUrl("jdbc:mysql://inner.com:3307/crawler?zeroDateTimeBehavior=convertToNull&characterEncoding=utf-8&useUnicode=true&useSSL=false");dataSourceProperties.setUsername("root");dataSourceProperties.setPassword("123456");} else if (dbKey.equals("local")) {dataSourceProperties.setUrl("jdbc:mysql://inner.com:3307/more_db?zeroDateTimeBehavior=convertToNull&characterEncoding=utf-8&useUnicode=true&useSSL=false");dataSourceProperties.setUsername("root");dataSourceProperties.setPassword("123456");}else {return null;}// 从源码中抄的,大概就是绑定数据return dataSourceProperties.initializeDataSourceBuilder().type(type).build();}}

注入到spring 容器

@Configuration
public class DbConfig {@Beanpublic DyDataSource dataSource() {return new DyDataSource(HikariDataSource.class);}
}

此时调用dao层需要手动设置租户信息


@RestController
@RequestMapping("/test")
public class TestController {@AutowiredTableTestServiceImpl testService;@GetMapping("getById")public TableTest getById(){DyDataSource.setDbKey("uuu");return testService.getById(1);}
}

总结

虽然代码实现了但是还有些隐患

  • 目前的dataSource 只有新建没有回收,这样的话需要我们去写一个生命周期的维护,否则 dataSource 越来越多,但是可能需要用到的不足10%,因为dataSource 多了,其中的数据库连接也是一直存活的,极端情况下会占用大量的tcp连接
  • 在初始化dataSource那块的代码虽然加了dcl但是也是有小概率出现线程问题(自行百度 dcl 缺陷)

第一个问题兴许可以通过redis这种做lru的过期淘汰策略,但是第二种目前还没找到方案,有想法的小伙伴可以评论下,让我学习下,谢谢!

mybatis plus+spring boot 多租户动态数据源实现方案相关推荐

  1. Spring Boot + Mybatis 配合 AOP 和注解实现动态数据源切换配置

    Spring Boot + Mybatis 配合 AOP 和注解实现动态数据源切换配置 前言: 1. 数据库准备: 2. 环境准备: 3.代码部分 4. 测试: 5.等等 6.配合注解实现 7 .测试 ...

  2. (转)Spring Boot通过ImportBeanDefinitionRegistrar动态注入Bean

    转自: Spring Boot通过ImportBeanDefinitionRegistrar动态注入Bean - 掘金在阅读SpringBoot源码时,看到SpringBoot中大量使用ImportB ...

  3. Spring Boot整合Jpa多数据源

    Spring Boot整合Jpa多数据源 本文是Spring Boot整合数据持久化方案的最后一篇,主要和大伙来聊聊Spring Boot整合Jpa多数据源问题.在Spring Boot整合JbdcT ...

  4. 13、Spring Boot 2.x 多数据源配置

    1.13 Spring Boot 2.x 多数据源配置 完整源码: Spring-Boot-Demos 转载于:https://www.cnblogs.com/Grand-Jon/p/9999779. ...

  5. Spring Boot 2.0 多数据源编程 jdbcUrl is required with driverClassName

    转载:https://my.oschina.net/chinesedragon/blog/1647846 Spring Boot 2.0 多数据源编程 在Spring Boot 1.5.x之前,多数据 ...

  6. mybatis 配置_配置Mybatis在Spring Boot工程中的整合

    配置Mybatis在Spring Boot工程中的整合包,设置mybatis的实体类别名,输出执行sql语句配置项. 分析: 添加启动器依赖: 配置Mybatis:实体类别名包,日志,映射文件等: 配 ...

  7. Spring Boot集成Quartz动态实现数据库任务

    1. Quartz简介 1.1. 什么是Quartz Quartz是一个开源的任务调度框架.作用是基于定时.定期的策略来执行任务. 它是OpenSymphony开源组织在Job scheduling领 ...

  8. struts、hibernate、spring、 mybatis、 spring boot 等面试题汇总

    1.谈谈你对Struts的理解. 答: 1. struts是一个按MVC模式设计的Web层框架,其实它就是一个大大的servlet,这个Servlet名为ActionServlet,或是ActionS ...

  9. Spring Boot骚操作-多数据源Service层封装

    原文:https://www.pdai.tech/md/spring/springboot-data-multi.html mysql, es, mongodb 三个数据源用配置文件方式连接,JPA只 ...

  10. SpringBoot+Mybatis 实现动态数据源切换方案

    背景 最近让我做一个大数据的系统,分析了一下,麻烦的地方就是多数据源切换抽取数据.考虑到可以跨服务器跨数据库抽数,再整理数据,就配置了这个动态数据源的解决方案.在此分享给大家. 实现方案 数据库配置文 ...

最新文章

  1. 日本16岁编程少年,课余打造一款新冠感染追踪App
  2. EMNLP 2019中和BERT相关的一些论文介绍
  3. Hi Table定义未来电视!祝贺海信发布S7社交电视! ​
  4. VS 2015 开发Android底部导航条----[实例代码,多图]
  5. java基础---try后小括号(1.7后IO流的关闭方式)
  6. CSAPP--整数的表示
  7. 【原型设计】第五节:Axure RP9 交面交互的使用说明 02 显示隐藏元素
  8. spring 4.0下集成webservice
  9. 32.从1到n整数中1出现的次数
  10. URL地址 长度超出限制问题解决
  11. 新浪微博数据采集以及人群画像分析
  12. windows下jenkins批处理执行git pull失败的原因
  13. 大型网站的架构技术-学习自李智慧的书
  14. java 读取本地配置文件 Properties
  15. CSDN 下载 版块问题解决日志
  16. LeetCode 分类练习(四):查找2
  17. jsp+ssh2+mysql实现的CRM客户关系管理系统
  18. 百汇BCR:通过K线可以判断出外汇市场有哪些形态?
  19. android studio 使用CMAKE,在terminal终端里敲cmake命令
  20. 不知不觉好感为“零”!录取机会少一半-程序员面试时,有哪些减分项?(注意避坑)

热门文章

  1. STM32——库函数版——数码管流动显示程序
  2. 极客大学产品经理训练营 用例Use Case 第8课总结
  3. html%3ca%3e标签改字体颜色,HTML URL Encoding 参考
  4. java求两个时间相差月_java计算两个时间相差几个月
  5. 决定论的科学家认为,自我是大脑的随附现象,自由意志是一种幻觉
  6. 304.二维区域和检索-矩阵不可变
  7. appscan无法连接到服务器_GTA5无法连接R星服务器怎么解决?无法连接解决方法
  8. 数据结构之--series,DataFrame.use python and pandas for data mining
  9. 【GYM-100889 D】Dicy Numbers【数学推导求解】
  10. 【POJ1276】【多重背包】凑货币问题