当前位置: 首页>数据库>正文

多schema多租户 表名 多租户多数据源

最近遇到了做多数据源多需求,我们多系统是基于多租户多,要求是不同多租户能访问不同多数据源,而达到提高性能和良好的容灾能力。

我们是基于druid+mysql+springboot的:

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

AbstractRoutingDataSource

org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 源码的介绍:

/**
 Abstract {@link javax.sql.DataSource} implementation that routes {@link #getConnection()}
  * calls to one of various target DataSources based on a lookup key. The latter is usually
  * (but not necessarily) determined through some thread-bound transaction context.
  *
  * @author Juergen Hoeller
  * @since 2.0.1
  * @see #setTargetDataSources
  * @see #setDefaultTargetDataSource
  * @see #determineCurrentLookupKey()
  */

AbstractRoutingDataSource就是DataSource的抽象,基于lookup key的方式在多个数据库中进行切换。重点关注setTargetDataSources,setDefaultTargetDataSource,determineCurrentLookupKey三个方法。那么AbstractRoutingDataSource就是Spring多数据源的关键了。

  1. setTargetDataSources设置备选的数据源集合
  2.  setDefaultTargetDataSource设置默认数据源
  3. determineCurrentLookupKey决定当前数据源的对应的key

但是好像3个方法都没有包含切换数据库的逻辑啊!仔细阅读源码发现一个方法,determineTargetDataSource方法,其实它才是获取数据源的实现。源码如下:

//切换数据库的核心逻辑
     protected DataSource determineTargetDataSource() {
         Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
         Object lookupKey = determineCurrentLookupKey();
         DataSource dataSource = this.resolvedDataSources.get(lookupKey);
         if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
             dataSource = this.resolvedDefaultDataSource;
         }
         if (dataSource == null) {
             throw new IllegalStateException
               ("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
         }
         return dataSource;
     }
     //之前的2个核心方法
     public void setTargetDataSources(Map<Object, Object> targetDataSources) {
         this.targetDataSources = targetDataSources;
     }
     public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
         this.defaultTargetDataSource = defaultTargetDataSource;
     }

简单说就是,根据determineCurrentLookupKey获取的key,在resolvedDataSources这个Map中查找对应的datasource!,注意determineTargetDataSource方法竟然不使用的targetDataSources!

那一定存在resolvedDataSources与targetDataSources的对应关系。接着翻阅代码,发现一个afterPropertiesSet方法(Spring源码中InitializingBean接口中的方法),这个方法将targetDataSources的值赋予了resolvedDataSources。源码如下
 

@Override
     public void afterPropertiesSet() {
         if (this.targetDataSources == null) {
             throw new IllegalArgumentException("Property 'targetDataSources' is required");
         }
         this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
         for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
             Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
             DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
             this.resolvedDataSources.put(lookupKey, dataSource);
         }
         if (this.defaultTargetDataSource != null) {
             this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
         }
     }

afterPropertiesSet 方法,熟悉Spring的都知道,它在bean实例已经创建好,且属性值和依赖的其他bean实例都已经注入以后执行。

也就是说调用,targetDataSources,defaultTargetDataSource的赋值一定要在afterPropertiesSet前边执行。

AbstractRoutingDataSource简单总结:

AbstractRoutingDataSource,内部有一个Map<Object,DataSource>的域resolvedDataSources
determineTargetDataSource方法通过determineCurrentLookupKey方法获得key,进而从map中取得对应的DataSource。
setTargetDataSources 设置 targetDataSources
setDefaultTargetDataSource 设置 defaultTargetDataSource,
targetDataSources和defaultTargetDataSource 在afterPropertiesSet分别转换为resolvedDataSources和resolvedDefaultDataSource。
targetDataSources,defaultTargetDataSource的赋值一定要在afterPropertiesSet前边执行。

简单写下为代码逻辑:

1、先写一个类继承AbstractRoutingDataSource,实现determineCurrentLookupKey方法,和afterPropertiesSet方法。afterPropertiesSet方法中调用setDefaultTargetDataSource和setTargetDataSources方法之后调用super.afterPropertiesSet。

2、定义一个切面在事务切面之前执行,确定真实数据源对应的key

3、用ThreadLocal传递真实数据源对应的key

4、定义一个druidDataSourceCreator类,每次创建数据源都从这里取

参考文章:1、

                  2、如果自己不想实现可以使用mybatis-plus的实现https://github.com/baomidou/dynamic-datasource-spring-boot-starter

                        更建议是自己参考其设计模式进行设计

               

 

 

 


https://www.xamrdz.com/database/6uq1937358.html

相关文章: