多租户多数据源实现

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

我们是基于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 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(this.targetDataSources.size());
        for (Map.Entry 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的域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、https://blog.csdn.net/qq_36903131/article/details/89372011

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

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

                 3、https://blog.csdn.net/floor2011/article/details/100907108

 

 

 

你可能感兴趣的:(记录)