前言
Spring Boot本身自带数据源,网上的关于多数据源的分享也多多益善。大体思路一致。本次的分享思路也是如此,不过,我并没有修改(覆盖)原来系统的默认数据源,也就是说。我只做扩展,不做修改。
由于实现RDB持久化的框架众多,比如Mybatis、JPA等等,所有这次我的数据源跟这些框架都没有关系,我只做数据源的增强。
实现目标
- 不修改(覆盖)Spring Boot自带的数据源
- 跟持久化框架无关,只跟Spring Boot有关
- 可以在配置文件里动态配置多个数据源,不限个数
- 可以在项目里随时添加数据源,比如在项目启动后从数据库里添加多个数据源
实现思路
首先打开Spring Boot源码org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
可以看到:
@Nullable
private Map<Object, Object> targetDataSources;
// 默认的数据源
@Nullable
private Object defaultTargetDataSource;
private boolean lenientFallback = true;
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
// 这里存的就是多个数据源
@Nullable
private Map<Object, DataSource> resolvedDataSources;
@Nullable
private DataSource resolvedDefaultDataSource;
但这些可能不太满足我们的要求,比如这些字段都是private的,我们如果在子类里想用到这些属性,无法使用。于是我们大胆地决定,copy一份出来,我们自己改。如下:
// 默认数据源路由key
protected static final String DEFAULT_DATASOURCE_KEY = "default";
private Map<Object, Object> targetDataSources;
//根据bean名称指定默认数据源,后面会提到为什么会是这个名称
@Resource(name = "defaultDataSource")
private Object defaultTargetDataSource;
private boolean lenientFallback = true;
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
//protected 子类可用
protected Map<Object, DataSource> resolvedDataSources;
private DataSource resolvedDefaultDataSource;
同样,我们还是写一个类继承以上copy的类,然后实现抽象接口
@Override
protected Object determineCurrentLookupKey() {
//线程级切换,判断是哪个source,如果未指定就是默认数据源
String dataSourceKey = DataSourceContextHolder.getDataSource() == null ? DEFAULT_DATASOURCE_KEY : DataSourceContextHolder.getDataSource();
logger.trace("Current DataSource is [{}]", dataSourceKey);
return dataSourceKey;
}
那么如果让默认数据源的beanName
变成defaultDataSource
,Spring Boot给我们提供的数据源beanName
是dataSource
。那怎么办呢。我们都知道:Spring加载bean的时候先转成化BeanDefinition
,然后再实例化成bean。Spring给我们提供了"接口"让我们重写或自定义bean,哪怕你这个bean未定义,甚至class也不在此项目里,都可以作为一个bean加进来,这里说多了,题外话。言归正传,如果实现,看代码:
private static final String DATASOURCE_BEAN_NAME = "dataSource";
//....
// 此方法就可以重写bean的定义
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
//。。。
//取得已经注册的数据源
BeanDefinition beanDefinition = registry.getBeanDefinition(DATASOURCE_BEAN_NAME);
//移除并添加为defaultDataSource的数据源
registry.removeBeanDefinition(DATASOURCE_BEAN_NAME);
//看,我们这里将默认数据源重新定义成defaultDataSource
registry.registerBeanDefinition("defaultDataSource", beanDefinition);
//新建动态数据源,并注册为系统数据源
GenericBeanDefinition dynamicBeanDefinition = new GenericBeanDefinition();
dynamicBeanDefinition.setBeanClass(DefaultDynamicDataSource.class);
dynamicBeanDefinition.setSynthetic(true);
dynamicBeanDefinition.setPrimary(true);
//初始化的路由列表
//设置bean的属性等
//注册给Spring容器
registry.registerBeanDefinition(DATASOURCE_BEAN_NAME, dynamicBeanDefinition);
}
这些就可以实现了多数据源。下面就是锦上添花的事情了,用aop实现指定数据源,可以在多数据源类里添加一个方法addDatasource
就可以动态添加数据源了。这里就不一一详细描述了。代码已开源,文档已提供:
源码: https://github.com/zhouxx/boot-plus/ 模块之boot-plus-core-datasource
文档: https://zhouxx.github.io/boot-plus/#/README
欢迎提出问题一起讨论。