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;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
而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;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
我们要做的就是
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);
1
2
3
4
5
6
7
8
9
10
11重写determineTargetDataSource()返回获取的数据源对应的key,
@Override protected Object determineCurrentLookupKey() { return "db" + DBContextHolder.getDBKey(); }
1
2
3
4
# 实现重点
- org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
- spring 提供的这个类是本次实现的核心,能够让我们实现运行时多数据源的动态切换,但是数据源是需要事先配置好的,无法动态的增加数据源。
- 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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
决定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;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
从上面源码可以看出它继承了 AbstractDataSource,而 AbstractDataSource 是 javax.sql.DataSource 的实现类,拥有 getConnection()方法。获取连接的 getConnection()方法中,重点是**determineCurrentLookupKey()**方法,它的返回值就是你所要用的数据源 dataSource 的 key 值,有了这个 key 值,resolvedDataSource(这是个 map,由配置文件中设置好后存入 targetDataSources 的,通过 targetDataSources 遍历存入该 map)就从中取出对应的 DataSource,如果找不到,就用配置默认的数据源。
看完源码,我们可以知道,只要扩展 AbstractRoutingDataSource 类,并重写其中的determineCurrentLookupKey()方法返回自己想要的 key 值,就可以实现指定数据源的切换!