Loading

Spring 动态数据源切换

Spring 动态数据源切换

Spring boot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前,设置使用的数据源。实现可动态路由的数据源,在每次数据库查询操作前执行。它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。 determineCurrentLookupKey() 返回的是数据源的key,需要根据key去resolvedDataSources(一个map)中去得到key对应的数据源,获取具体数据源的方法是determineTargetDataSource()

这下原理就清晰了,在创建动态数据源时,建立一个key到真实数据源的映射,放在哈希表里。在每次调用dao层的方法时,环绕通知的增强会根据当前dao所在的包,往threadLocal设置不同的key,在mybatis从datasource获取数据源时,从threadLocal拿到刚刚设置的key,从哈希表里拿到对应真实数据源,如果key为空,则获取默认的数据源。

几个重要的参数与方法

     
       存放数据源的map
    private Map<Object, Object> targetDataSources;
    
         默认数据源
    private Object defaultTargetDataSource;
   
   
      存放数据源的map   是由targetDataSources遍历赋值的
    private Map<Object, DataSource> resolvedDataSources;
       
       默认数据源      由defaultTargetDataSource赋值
    private DataSource resolvedDefaultDataSource;



AbstractRoutingDataSource 是实现了InitializingBean接口的      @Bean所在方法执行完成后,会调用此方法
主要是targetDataSources 赋值到resolvedDataSources   defaultTargetDataSource赋值到resolvedDefaultDataSource
public void afterPropertiesSet() {
    if (this.targetDataSources == null) {
        throw new IllegalArgumentException("Property 'targetDataSources' is required");
    } else {
        this.resolvedDataSources = new HashMap(this.targetDataSources.size());
        //遍历 targetDataSources 赋值resolvedDataSources
        this.targetDataSources.forEach((key, value) -> {
            Object lookupKey = this.resolveSpecifiedLookupKey(key);
            DataSource dataSource = this.resolveSpecifiedDataSource(value);
            this.resolvedDataSources.put(lookupKey, dataSource);
        });
        if (this.defaultTargetDataSource != null) {
            //默认使用的数据源
            this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
        }

    }
}

而determineTargetDataSource()方法是决定spring容器连接那个数据源

决定spring 使用哪个数据源  
protected DataSource determineTargetDataSource() {
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    //该方法就是我们重写的方法################################
    Object lookupKey = this.determineCurrentLookupKey();
    DataSource 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 + "]");
    } else {
        return dataSource;
    }
}

我们要做的就是

  1. targetDataSources中 放入数据源map

     // 创建数据源map
            Map<Object, Object> targetDataSources = new HashMap<>();
            for (String dbInfo : dataSourceMap.keySet()) {
                Map<String, Object> objMap = dataSourceMap.get(dbInfo);
                targetDataSources.put(dbInfo, new DriverManagerDataSource(objMap.get("url").toString(), objMap.get("username").toString(), objMap.get("password").toString()));
            }
    
            // 设置数据源
            DynamicDataSource dynamicDataSource = new DynamicDataSource();
              //设置targetDataSources 
            dynamicDataSource.setTargetDataSources(targetDataSources);
    
  2. 重写determineTargetDataSource()返回获取的数据源对应的key,

     @Override
        protected Object determineCurrentLookupKey() {
            return "db" + DBContextHolder.getDBKey(); 
        }
    



实现重点

  1. org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
  2. spring 提供的这个类是本次实现的核心,能够让我们实现运行时多数据源的动态切换,但是数据源是需要事先配置好的,无法动态的增加数据源。
  3. Spring 提供的 Aop 拦截执行的 mapper,进行切换判断并进行切换。

注:另外还有一个就是 ThreadLocal 类,用于保存每个线程正在使用的数据源。



AbstractRoutingDataSource 解析

 public abstract class AbstractRoutingDataSource extends AbstractDataSource 
implements InitializingBean{
    @Nullable
    private Map<Object, Object> targetDataSources;

    @Nullable
    private Object defaultTargetDataSource;

    @Override
    public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
    }
    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;
    }
    @Override
    public void afterPropertiesSet() {
        if (this.targetDataSources == null) {
            throw new IllegalArgumentException("Property 'targetDataSources' is required");
        }
        this.resolvedDataSources = new HashMap<>(this.targetDataSources.size());
        this.targetDataSources.forEach((key, value) -> {
            Object lookupKey = resolveSpecifiedLookupKey(key);
            DataSource dataSource = resolveSpecifiedDataSource(value);
            this.resolvedDataSources.put(lookupKey, dataSource);
        });
        if (this.defaultTargetDataSource != null) {
            this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
        }
    }

决定spring 使用哪个数据源  
protected DataSource determineTargetDataSource() {
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    //该方法就是我们重写的方法
    Object lookupKey = this.determineCurrentLookupKey();
    DataSource 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 + "]");
    } else {
        return dataSource;
    }
}

从上面源码可以看出它继承了 AbstractDataSource,而 AbstractDataSource 是 javax.sql.DataSource 的实现类,拥有 getConnection()方法。获取连接的 getConnection()方法中,重点是determineCurrentLookupKey()方法,它的返回值就是你所要用的数据源 dataSource 的 key 值,有了这个 key 值,resolvedDataSource(这是个 map,由配置文件中设置好后存入 targetDataSources 的,通过 targetDataSources 遍历存入该 map)就从中取出对应的 DataSource,如果找不到,就用配置默认的数据源。

看完源码,我们可以知道,只要扩展 AbstractRoutingDataSource 类,并重写其中的determineCurrentLookupKey()方法返回自己想要的 key 值,就可以实现指定数据源的切换!

posted @ 2023-09-01 17:40  花园SON  阅读(4)  评论(0编辑  收藏  举报