Spring实现动态数据源:AbstractRoutingDataSource

293 阅读2分钟

在Spring里抽象类AbstractRoutingDataSource,通过扩展这个类实现根据不同的请求切换数据源。

AbstractRoutingDataSource 中比较重要的属性

//指定目标数据源的映射,以查找键作为键。映射的值可以是对应的DataSource实例,也可以是数据源名称String(通过datasourcellookup解析)。
@Nullable
private Map<Object, Object> targetDataSources;

//解析后的查找键和数据源的不可修改映射
@Nullable
private Map<Object, DataSource> resolvedDataSources;

AbstractRoutingDataSource 实现InitializingBean :把所有放入targetDataSources和defaultTargetDataSource的DataSource放入resolvedDataSources

@Override
public void afterPropertiesSet() {
   if (this.targetDataSources == null) {
      throw new IllegalArgumentException("Property 'targetDataSources' is required");
   }
   this.resolvedDataSources = CollectionUtils.newHashMap(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);
   }
}

AbstractRoutingDataSource继承AbstractDataSource,如果声明一个类DynamicDataSource继承AbstractRoutingDataSource后,DynamicDataSource本身就相当于一种数据源。所以AbstractRoutingDataSource必然有getConnection()方法获取数据库连接。如下:

@Override
public Connection getConnection() throws SQLException {
    return determineTargetDataSource().getConnection();
}

@Override
public Connection getConnection(String username, String password) throws SQLException {
    return determineTargetDataSource().getConnection(username, password);
}

AbstractRoutingDataSourcegetConnection()方法里实际是调用determineTargetDataSource()返回的数据源的getConnection()方法。接着看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;
}

大致流程为,通过determineCurrentLookupKey()方法获取一个key,通过key从resolvedDataSources中获取数据源DataSource对象。determineCurrentLookupKey()是个抽象方法,需要继承AbstractRoutingDataSource的类实现;而resolvedDataSources是一个Map<Object, DataSource>,里面应该保存当前所有可切换的数据源。

  1. 首先把不同的DataSource注入到Spring
		@Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(DruidProperties druidProperties)
    {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(dataSource);
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource(DruidProperties druidProperties)
    {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(dataSource);
    }
  1. 实例化DynamicDataSource,把两个DataSource放入AbstractRoutingDataSource.``targetDataSources 中,方便后面取出。
		@Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource(DataSource masterDataSource)
    {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
        setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }
  1. DynamicDataSource 继承 AbstractRoutingDataSource 实现determineCurrentLookupKey ,根据不同的规则返回不同的id来进行数据源的选择。
💡 有多种实现,比如自定义注解,通过注解来标识使用什么数据源,通过不同的配置标识等等
public class DynamicDataSource extends AbstractRoutingDataSource
{
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
    {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey()
    {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}