Spring Boot 构建多租户Saas软件架构,实现动态切换数据源

  • 概述
  • 应用场景
  • 维护、识别和路由租户数据源
  • 项目构建

  • 旧版-租户v1.0:实现动态源切换,需手动创建好不同数据源的数据库和表结构;
  • 新版-租户v2.0:实现了动态源切换;且新增租户,动态创建数据库和基础表结构和数据。

备注:以上两种适合企业级数据库级别隔离,具体可参考 => 源码点击这里

  • baomidou/dynamic-datasource

备注:也就是苞米豆的MyBatis-Plus,适合中小型企业,数据级别隔离,通过租户字段区分数据,支持多数据源支持多租户插件(引用)

点击这里

如上提到的独立数据库来存放租户信息(master库)。
tenant_info 表结构如下:
笔者表结构
目前 tenant_info 表数据如下:
表数据
还需要两个或多个租户库,用来测试切换不同数据源读取不同的租户库里面的信息。例如创建tenant1 和 tenant2两个库,都创建一个User表,存放对应租户的不同客户信息。

User 表结构如下:在这里插入图片描述

到这里,数据库模拟场景已经准备完毕,接下来下面就是真正的核心环节!

项目环境及pom依赖我不多说,到时候提供项目demo,参考即可!

先将 源码 拉取下来,这篇文章只将参考源码进行讲解,核心代码都在config文件中!

1、核心类AbstractRoutingDataSource的实现类DynamicDataSource

AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前,设置使用的数据源。实现可动态路由的数据源,在每次数据库查询操作前执行。它的抽象方法determineCurrentLookupKey() 决定使用哪个数据源

实现逻辑:

  1. 定义DynamicDataSource类继承抽象类AbstractRoutingDataSource,并实现了determineCurrentLookupKey()方法。
  2. 把配置的多个数据源会放在AbstractRoutingDataSource的 targetDataSources和defaultTargetDataSource中,然后通过afterPropertiesSet()方法将数据源分别进行复制到resolvedDataSources和resolvedDefaultDataSource中。
  3. 调用AbstractRoutingDataSource的getConnection()的方法的时候,先调用determineTargetDataSource()方法返回DataSource在进行getConnection()。

我们自定义的类 DynamicDataSource主要实现了determineCurrentLookupKey()setTargetDataSources() 这两个方法,并且定义了一个 Map<Object, Object> 存放多数据源的信息,然后将Map信息set到setTargetDataSources()中,再调用一遍afterPropertiesSe()。

determineCurrentLookupKey():实现数据源切换要扩展的方法,该方法的返回值就是项目中所要用的DataSource的key值,拿到该key后就可以在resolvedDataSource中取出对应的DataSource,如果key找不到对应的DataSource就使用默认的数据源,前提是指定了默认数据源。
setTargetDataSources():设置多数据源,入参是Map对象。设置完后记得调用一遍afterPropertiesSe()重置AbstractRoutingDataSource中 resolvedDataSources和resolvedDefaultDataSource。

2、动态数据源上下文DynamicDataSourceContextHolder

用 ThreadLocal (线程局部变量,线程安全) 存放当前租户的key,tenant_info 表的 tenant_id字段,改变 ThreadLocal 的值实现切换数据源,然后在 DynamicDataSource 的 determineCurrentLookupKey()方法中获取ThreadLocal 的值。

1、(无事务下)切换数据源必须在调用数据持久层Dao层之前进行。原因:调用Dao层之前,先执行一遍determineCurrentLookupKey()获取当前租户的key,切换一次数据源。
2、(有事务下)切换数据源必须在调用业务层Service层之前进行,也就是开启事务之前。原因:我们一般在service的实现类的方法上@Transactional事务注解,这里就开启了事务。我们可以在控制层Controller层里切换数据源,如果觉得代码臃肿,还可以使用Aop自定义注解的方式实现切换数据源。

3、初始化动态数据源DynamicDataSourceInit

这个类主要是读取数据库的数据源,实现灵活性(所有指定的默认数据源一般都是独立数据库,也就是有存放租户信息的数据库,才可以读取多租户的数据源信息)。用到的数据源信息也就数据库名称、链接地址、用户名、密码。

1、 @PostConstruct:好多人以为是Spring提供的,其实是Java自己的注解,用来修饰一个 非静态的void()方法 。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。
2、加载默认数据源除外的其他数据源,put到DynamicDataSource的Map中(可以在put之前验证该数据源是否可以连接成功,连接成功则put进去,否则抛出异常),然后将Map信息set到setTargetDataSources()方法中,设置完后记得调用一遍afterPropertiesSe()重置AbstractRoutingDataSource中 resolvedDataSources和resolvedDefaultDataSource。

4、初始化Bean配置 MybatisConfig

这个类主要初始化了默认数据源的DataSource(加载application.yml配置文件的数据库配置)、初始化Mybatis的SqlSessionFactoryBean、配置事务管理TransactionManager。

总结:
大概执行过程就是,先在MybatisConfig类中从yml文件读取数据库配置来获取默认数据源,再执行AbstractRoutingDataSource类来设置数据源setTargetDataSources()和重置afterPropertiesSet()。

然后再走DynamicDataSourceInit初始化多租户的动态数据源,配置了默认数据源,则可以连接读取多数据源信息(Dao层之前执行一遍determineCurrentLookupKey(),获取当前数据源。由于我在DynamicDataSourceContextHolder类的ThreadLocal指定了默认数据源的key,或者通过AbstractRoutingDataSource类的setDefaultTargetDataSource指定了默认目标数据源,所以取的数据源是独立数据库的key,即“master”),再执行AbstractRoutingDataSource类来设置数据源setTargetDataSources()和重置afterPropertiesSet()。

然后在UserServiceImpl(无事务下)通过DynamicDataSourceContextHolder的setDataSourceKey(“tenant1”)或setDataSourceKey(“tenant2”)改变ThreadLocal的值,来切换数据源,读取对应租户的客户信息。

可别忘了在启动类上配置这个!

//不让Spring自动加载数据源配置,我们将采取手动配置数据源的方式
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

源码点击这里 ,这是小编的个人笔记哦,喜欢的给小编给个赞!!

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐