Spring中实现动态数据源切换,基于AbstractRoutingDataSource

背景

在项目开发过程中,我们可能会遇到一个场景:某个类型数据源有多个数据源实例,需要我们按照不同的请求切换到不同数据源去。

而目前绝大多数java应用都是基于Spring框架来开发,我们很多时候相关的数据源连接都是交给了Spring框架去管理,这就需要Spring能够支持动态数据源切换。

方案

Spring中预留了这个接口,通过AbstractRoutingDataSource能够动态切换数据源。

java 复制代码
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

这是一个抽象类,预留了一个抽象方法:

java 复制代码
protected abstract Object determineCurrentLookupKey();

我们知道,数据源一般都会提供一个getConnection方法来获取一个连接,在AbstractRoutingDataSource 实现如下:

java 复制代码
	@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;
	}

可以看到,AbstractRoutingDataSource 获取连接的主要逻辑就是通过determineCurrentLookupKey获取到一个数据源的关联key,然后从resolvedDataSources中去获取。

resolvedDataSources的初始化,则放在afterPropertiesSet中:

java 复制代码
	@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);
		}
	}

这里起始就是通过targetDataSources中指定的数据源复制到resolvedDataSources 中去。因此如果多数源是固定的,那么只需要实现determineCurrentLookupKey方法即可。但是如果多数据源不固定,比如可能会有数据源的变更,那么这种实现是不能够支持,因为这种实现从服务启动的视乎,后续数据源就不能发生变更,这需要我们自己实现determineTargetDataSource.

下面是一个参考实现:

java 复制代码
public class DataSourceContextHolder {

    private static final ThreadLocal<String> DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>();


    public static void switchDataSource(String key){
        log.info("Switch to data source:" + key);
        DATASOURCE_CONTEXT_KEY_HOLDER.set(key);
    }
    public static String getDataSourceKey(){
        return DATASOURCE_CONTEXT_KEY_HOLDER.get() ;
    }

}

public class DynamicDataSource extends AbstractRoutingDataSource {

    private Map<Object, Object> targetDataSources = new HashMap<>();
    private Map<Object, DataSource> dataSources = new HashMap<>();
    public DynamicDataSource (){
        super.setDefaultTargetDataSource(null);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }
 @Override
    protected DataSource determineTargetDataSource() {
        Object dataSourceKey = determineCurrentLookupKey();
        return dataSources.get(dataSourceKey);
    }
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder .getDataSourceKey();
    }
    public synchronized void addDataSource(String key, DataSource dataSource){
        targetDataSources.put(key,dataSource);
        dataSources.put(key,dataSource);
        log.info("add tenant dynamic dataSource for tenantId = {} ",key);
    }
}

这样我们通过DataSourceContextHolder 来调整当前线程关联的数据源。

相关推荐
2501_941865631 小时前
从事件驱动到异步架构的互联网工程语法构建与多语言实践分享
java·开发语言·jvm
全靠bug跑8 小时前
Spring Cache 实战:核心注解详解与缓存过期时间配置
java·redis·springcache
聆风吟º8 小时前
【数据结构手札】空间复杂度详解:概念 | 习题
java·数据结构·算法
档案宝档案管理8 小时前
档案宝自动化档案管理,从采集、整理到归档、利用,一步到位
大数据·数据库·人工智能·档案·档案管理
计算机程序设计小李同学8 小时前
基于SpringBoot的个性化穿搭推荐及交流平台
java·spring boot·后端
是一个Bug8 小时前
50道核心JVM面试题
java·开发语言·面试
朱朱没烦恼yeye8 小时前
java基础学习
java·python·学习
她和夏天一样热9 小时前
【观后感】Java线程池实现原理及其在美团业务中的实践
java·开发语言·jvm
C_心欲无痕9 小时前
浏览器缓存: IndexDB
前端·数据库·缓存·oracle
lkbhua莱克瓦249 小时前
进阶-索引3-性能分析
开发语言·数据库·笔记·mysql·索引·性能分析