一、多数据源的使用场景
实际开发中,经常可能遇到在一个应用中可能需要访问多个数据库的情况。以下是两种典型场景:
1.1 业务复杂/数据量大
一个应用需要用到不同库的数据,就需要连不同的数据库。
比如我们的项目里,一个服务,同时需要用自己mysql表的数据,还要连上大数据的库获取数据。
1.2 读写分离
为了解决数据库的读性能瓶颈(读比写性能更高, 写锁会影响读阻塞,从而影响读的性能)。
很多数据库拥主从架构。也就是,一台主数据库服务器,是对外提供增删改业务的生产服务器;另一(多)台从数据库服务器,主要进行
读的操作。ꞏ
可以通过中间件(ShardingSphere、mycat、mysql-proxy 、TDDL …), 但是有一些规模较小的公司,没有专门的中间件团队搭建读写分
离基础设施,因此需要业务开发人员自行实现读写分离
这里的架构与上图类似。不同的是,在读写分离中,主库和从库的数据库是一致的(不考虑主从延迟)。数据更新操作(insert、update、
delete)都是在主库上进行,主库将数据变更信息同步给从库。在查询时,可以在从库上进行,从而分担主库的压力。
二、如何实现多数据源
2.1 AbstractRoutingDataSource原理
spring框架中,spring-jdbc模块提供了AbstractRoutingDataSource,其内部可以包含了多个DataSource,然后在运行时来动态的访问哪个数据库。这种方式访问数据库的架构图如下所示:
应用直接操作的是AbstractRoutingDataSource的实现类,告诉AbstractRoutingDataSource访问哪个数据库,然后由AbstractRoutingDataSource从事先配置好的数据源(ds1、ds2)选择一个,来访问对应的数据库。
1.当执行数据库持久化操作,只要集成了Spring就一定会通过DataSourceUtils获取Connection
2. 通过Spring注入的DataSource获取Connection 即可执行数据库操作
所以思路就是:只需配置一个实现了DataSource的Bean, 然后根据业务动态提供Connection即可
3.其实Spring已经提供一个DataSource实现类用于动态切换数据源——AbstractRoutingDataSource
4.分析AbstractRoutingDataSource即可实现动态数据源切换:
通过这个类可以实现动态数据源切换。如下是这个类的成员变量
private Map<Object, Object> targetDataSources;
private Object defaultTargetDataSource;
private Map<Object, DataSource> resolvedDataSources;
- targetDataSources 保存了key和数据库连接的映射关系 (所有数据源)
- defaultTargetDataSource标识默认的连接(默认数据源)
- resolvedDataSources这个数据结构是通过targetDataSources构建而来,存储结构也是数据库标识和数据源的映射关系
而AbstractRoutingDataSource实现了InitializingBean接口,并实现了afterPropertiesSet方法。afterPropertiesSet方法是初始化bean的时候执行,通常用作数据初始化。resolvedDataSources就是在这里赋值
@Override
public void afterPropertiesSet() {
//...
this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size()); //初始化resolvedDataSources
//循环targetDataSources,并添加到resolvedDataSources中
for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
this.resolvedDataSources.put(lookupKey, dataSource);
}
//...
}
5.所以,我们只需创建AbstractRoutingDataSource实现类DynamicDataSource然后 始化targetDataSources和key为数据源标识(可以是字符串、枚举、都行,因为标识是Object)、defaultTargetDataSource即可
6.后续当调用AbstractRoutingDataSource.getConnection 会接着调用提供的模板方法:determineTargetDataSource
7.通过determineTargetDataSource该方法返回的数据库标识 从resolvedDataSources 中拿到对应的数据源
8.所以,我们只需DynamicDataSource中实现determineTargetDataSource为其提供一个数据库标识
总结: 在整个代码中我们只需做4件大事:
1.定义AbstractRoutingDataSource实现类DynamicDataSource
2. 初始化时为targetDataSources设置 不同数据源的DataSource和标识、及defaultTargetDataSource
3. 在determineTargetDataSource中提供对应的数据源标识即可
4、切换数据源标识
2.2 通过AbstractRoutingDataSource实现动态数据源
1 . 配置多数据源 和 AbstractRoutingDataSource的自定义实现类:DynamicDataSource
配置多数据
server:
port: 8080
spring:
datasource:
druid:
ds1:
url: jdbc:mysql://localhost:3306/db_ds1?serverTimeZone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
ds2:
url: jdbc:mysql://localhost:3306/db_ds2?serverTimeZone=UTC&useUnicode=true&characterEncoding=UTF-8&a