spring boot 自动装配会通过 spring.datasource.*
为我们自动装配数据源,所以想要动态的切换数据源,第一件事是配置数据源,其次是怎么切换?最后何时切换?
原理解析(使用 AbstractRoutingDataSource 实现)
spring-jdbc 提供了 AbstractRoutingDataSource
在 getConnection()
时通过 lookup key
决定目标数据源,使用 AbstractRoutingDataSource 需要准备至少两个数据源,这在源码中也有体现:
一个默认数据源 + 动态匹配的数据源,
resolvedDataSources
是一个
Object
为 key,
DataSource
为 value 的 Map,可见这里 key 即充当了
lookup key
的角色。
在调用
getConnection
获取数据源时会调用
determineTargetDataSource
方法获取目标数据源,进而通过
determineCurrentLookupKey
方法获得当前的
lookup key
,再从
resolvedDataSources
中获得目标数据源。
AbstractRoutingDataSource
是一个抽象类,determineCurrentLookupKey
是其唯一的抽象方法,意味着子类只需在适当的时候修改当前的 lookup key
就能实现动态的更改当前的数据源。
配置数据源
配置数据源也就是给 AbstractRoutingDataSource
的 defaultTargetDataSource
和 resolvedDataSources
进行赋值。
这里我们将数据源配置在 properties 文件中,通过工具类解析进行配置。
在 properties 中配置数据源
demo 项目为 spring boot 项目,配置文件采用 yml 格式:
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/db?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
username: root
password: 1234
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
multi:
ds-keys: db1,db2
db1:
url: jdbc:mysql://127.0.0.1:3306/db11?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
username: root
password: 1234
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
db2:
url: jdbc:mysql://127.0.0.1:3306/db12?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
username: root
password: 1234
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
spring.datasource 前缀的将被解析为默认数据源, spring.datasource.multi 前缀的则作为动态数据源进行解析。
spring.datasource.multi.ds-keys
是比较关键的属性,这个参数定义了项目中所有的动态数据源的 key,如上述代码所示,spring.datasource.multi.ds-keys: db1,db2
:表明共有两个动态数据源,key 分别为 db1 和 db2 ,进行解析时将解析如下两个前缀配置的数据源:
- spring.datasource.multi.db1
- spring.datasource.multi.db2
当然这个规则是我自己定义的,可以按需定义自己的规则并实现相应的解析。
解析数据源
使用 DynamicDataSourceBuilder 类来解析数据源,注册为 bean 并实现 EnvironmentAware 接口,在 setEnvironment
方法中开始进行解析。
initDefaultDataSource
和 initCustomDataSources
方法将解析得到默认数据源和动态数据源,默认数据源将赋值给 defaultDataSource
,targetDataSources
中是所有解析得到的数据源,包括默认数据源,其数据类型是一个 Map,Map 的 key 即为 Lookup key
。
初始化动态数据源
/**
* 初始化定制数据源
*/
private void initCustomDataSources(Environment env) {
// 读取配置文件获取定制数据源,也可以通过数据库获取数据源
String dsNames = env.getProperty(customDataSourceKeys);
for (String dsKey : dsNames.split(",")) {
DataSource ds = buildDataSource(env, customDataSourcePrefix + "." + dsKey);
targetDataSources.put(dsKey, ds);
dataBinder(ds, env);
}
}
customDataSourceKeys 即为 spring.datasource.multi.ds-keys
,得到定制数据源前缀后进行构建,之后添加到 targetDataSources
中。
构建数据源
/**
* 创建 datasource.
*/
@SuppressWarnings("unchecked")
private DataSource buildDataSource(Environment env, String dsPrefix) {
try {
String prefix = dsPrefix + ".";
String dbpType = env.getProperty(prefix + DataSourcePropertyKey.type, defaultDataSourceType);
Class extends DataSource> dsType = (Class extends DataSource>) Class.forName(dbpType);
DataSource dataSource = DataSourceBuilder.create()
.driverClassName(env.getProperty(prefix + DataSourcePropertyKey.driverClassName))
.url(env.getProperty(prefix + DataSourcePropertyKey.url))
.username(env.getProperty(prefix + DataSourcePropertyKey.username))
.password(env.getProperty(prefix + DataSourcePropertyKey.password))
.type(dsType)
.build();
configDataSourcePool(dataSource);
return dataSource;
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
数据源的构建通过读取 properties 文件,并借助 DataSourceBuilder
类进行构建。configDataSourcePool
方法可对数据源对应的连接池进行配置。
这里需要注意的是 spring boot 2.* 对配置文件的读取 API 有比较大的变动,可参考这里。