目录
2、替换targetDataSources存储的数据源信息(重要)
3、最后一步修改determineCurrentLookupKey()(重要)
masterDataSource方法(不重要)这里就是我们在yml文件中配置生效的原因:
afterPropertiesSet()方法,将targetDataSources内容放入到resolveDataSources中:
配置的默认数据源有什么用:(读懂这个方法就知道上边配置的作用了)
简介:
这里有一个需求,每个用户都有自己对应的数据库,但是只有一个登录页。这就要求后端判断每一个用户连接,动态的切换操作的数据库,使用户操作自己的数据库。而数据库的信息是保存在一个名为school的表中,这就需要我们先连接上默认数据库,读取school表记录,然后创建相应的数据源。
这是一个ruoyi项目,基于 spring boot+mybaits pluse+druid+Security 技术的项目。核心原理都是一样的,是不是ruoyi项目是不重要的,只要知道了原理,就可以为所欲为。
school表:其中定义了多个数据源信息:

ps:
- https://blog.csdn.net/qq_42892858/article/details/109579451 这是一篇以前写的文章,通过一个简单的案例,讲解了多数据源,但它也是不完整的,可以通过阅读大体了解下配置步骤。
- 虽然这是基于ruoyi项目,不过重点是多数据源的配置原理,这也是通用的
实现的原理:
spring的动态数据源核心类AbstractRoutingDataSource里保存了数据源的连接信息,所以我们想定义适应当前项目的多数据源配置也是去修改spring的这个类。如下图,就是AbstractRoutingDataSource类,可以知道:
- determineCurrentLookupKey() 方法能返回要操作数据源的key。而这是一个抽象方法,需要自定义实现。
- targetDataSources存放了数据源的定义信息,是一个map对象。可以猜到在访问数据库时先从determineCurrentLookupKey()方法获得key,在通过key来这里取出对应连接
- defaultTargetDataSource存储的是默认数据源,
- resolvedXxx才是应用实际访问的对象,也就是要将targetXxx数据存储在resolvedXxx内

实现的步骤:
1、在yml文件配置数据库信息,先连接上默认数据库。
因为我们要去默认库中读取多数据源信息。所以先连接上默认数据库。
这里是druid数据源,所以我们自定义的数据源也要实现druid配置(最好的方法是找到druid创建数据源的类,我们去调用,这样我们创建的数据源也就实现了druid定义的参数)

2、替换targetDataSources存储的数据源信息(重要)
@Component //这是一个组件
public class MultipleDataSources {
@Autowired
DynamicDataSource dynamicDataSource1; //spring对数据源的管理
@Autowired //这是存放数据源信息的表
private SchoolsService schoolsService;
@Value("${multipleDatabase.defaultDatabase}") //这是在yml配置定义的默认数据源
private String defaultDatabase;
//driuid参数类,对把配置文件中的链接配置设置到内
@Autowired
DruidProperties druidProperties;
@PostConstruct //作用是生成bean的时候运行这个方法,也就是在这个方法修改数据源信息
public void setDynamicDataSource(){
Map<Object, Object> targetDataSources = new HashMap<>();
List<Schools> schools = schoolsService.list();
for (Schools school : schools) {
//通过creatDruidDataSource方法将school信息生成数据源并放在targetDataSources中
targetDataSources.put(school.getDbName(),creatDruidDataSource(school));
}
//这里只是设置了数据源的定义
dynamicDataSource1.setTargetDataSources(targetDataSources);
dynamicDataSource1.setDefaultTargetDataSource(targetDataSources.get(defaultDatabase));
dynamicDataSource1.afterPropertiesSet();
//完成新增数据源后,将ruoyi写人数据源 获取 方法停用
DatabaseContextHolder.defaultDatabase = defaultDatabase;
dynamicDataSource1.fla = false;
}
public DataSource creatDruidDataSource(Schools school){
//创建druidDataSource
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
if (dataSource.getUsername() == null) {
dataSource.setUsername(school.getLoginName());
}
if (dataSource.getPassword() == null) {
dataSource.setPassword(school.getLoginPassword());
}
if (dataSource.getUrl() == null) {
dataSource.setUrl(school.getLoginUrlPrefix()+school.getDbName()+school.getLoginUrlSuffix());
}
if (dataSource.getDriverClassName() == null) {
dataSource.setDriverClassName(school.getLoginDriver());
}
//给这个数据源设置基础参数 直接调用 这样的配置文件中对druid人配置也会生效
DruidDataSource druidDataSource = druidProperties.dataSource(dataSource);
return druidDataSource;
}
}
- 2.1spring中有一个容器存放所有bean,所以我们修改也是去修改bean容器里AbstractRoutingDataSource。这里通过@Autowired注解获得bean容器的类。DynamicDataSource实现了AbstractRoutingDataSource,所以实现上修改的是DynamicDataSource类
@Autowired
DynamicDataSource dynamicDataSource1;2.2 我们创建的数据源也要是druid数据源,creatDruidDataSource方法将school信息创建数据源并返回。createDruidDataSource方法的写法是跟ruoyi中实现步骤一样的。后文有介绍ruoyi中的masterDataSource方法,有兴趣可以看一下。
2.3核心步骤:AbstractRoutingDataSource类中的targetDataSources存放了数据源的定义信息,defaultTargetDataSource存储了默认数据源。这里就是去将这两个参数设置成我们自定义的。但实际用到的参数是resolvedXxx参数,afterPropertiesSet()方法就是将targetXxx参数转化为resolvedXxx参数。后边有afterProperTiesSet()方法介绍,有兴趣可以看下如何转换的。
dynamicDataSource1.setTargetDataSources(targetDataSources);
dynamicDataSource1.setDefaultTargetDataSource(targetDataSources.get(defaultDatabase));
dynamicDataSource1.afterPropertiesSet();3、最后一步修改determineCurrentLookupKey()(重要)
在原理上说了配置多数据源最重要的就是修改TargetDataSource参数与determineCurrentLookupKey方法,TargetDataSource已经搞定了,现在只要自定义determineCurrentLookupKey()方法,让它返回的key是我们在TargetDataSource中存放的key就可以。所以我们要找到ruoyi中定义的dynamicDataSource()类,去修改它的determineCurrentLookupKey()就好了。可以使用ctrl+shift+f快速找到DynamicDataSource类完成修改。
如下图:fla参数是新加的,注意在setDynamicDataSource方法中,我们配置好TargetDataSource参数后,将fla改成了false。这是因为对数据的修改是项目编译完成之后执行的,所以刚开始是使用yml文件中定义的数据源,所以这里返回的key我们不去修改。而当我们替换成新的数据源时就要使用TargetDataSource中存储的key了。DataBaseContextHolder是我们自定义的,下边有这个这个类的介绍。

4、DatabaseContextHolder类(重要):
TreadLocal对象是一个线程对象,每个线程都有对应的值,也可以说A线程赋值a,B线程赋值b,下次A线程取出来的还是a,B线程取出来的还是b。
在本项目中是这么用的:
- 当用户登录前调用DatabaseContestHolder.setDataSourceType(),将contextHolder的值设置成defaultDatabase,这样就会到默认库实现Security中登录逻辑。并且将用户对应的数据库源的key值放入seesion中
- 那么当一个用户操作数据前,先将seesion中提取到要操作的数据源key,在将这个key存储到contextHolder中。那么spring通过determineCurrentLookupKey()拿到的key就是这个用户要操作的key了
public class DatabaseContextHolder {
public static String defaultDatabase = "";
private static final ThreadLocal<String> contextHolder = new ThreadLocal();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
String s = contextHolder.get();
if(s == null || s.equals("") || s.equals("null")){
return defaultDatabase;
}
return s;
}
}


ruoyi方法:
masterDataSource方法(不重要)这里就是我们在yml文件中配置生效的原因:
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(DruidProperties druidProperties)
{
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
DruidDataSource druidDataSource = druidProperties.dataSource(dataSource);
return druidDataSource;
}- 可见这是方法作用是生成一个bean,那么肯定会有地方会引入这个bean.往下翻 dataSource()就引用了masterDataSource生成的bean,而dataSource方法生成了DynamicDataSource bean,这个bean就是我们最终要修改的bean.

- @ConfigurationProperties("spring.datasource.druid.master") 在方法上使用@ConfigurationProperties,会将参数设置到返回的对象中。这里就是设置的url,name,password

DruidDataSource druidDataSource = druidProperties.dataSource(dataSource); 就是给drui数据源设置相关的参数。首先知道 druidProperties是一个bean,所有我们去调用这个方法,也要先获得这个bean,在去给我们新建的数据源设置参数。

afterPropertiesSet()方法,将targetDataSources内容放入到resolveDataSources中:

配置的默认数据源有什么用:(读懂这个方法就知道上边配置的作用了)
如果因为不了解ruoyi,觉得上边的配置复杂,可以好好读一下这个方法,就能更深入的了解了。可以把这个方法看作和外界唯一的接口,如果要操作数据,就要先通过这个接口获取数据源。甚至可以重写这个方法,让它根据我们的逻辑返回数据源。反正我们只要返回一个数据源就可以了!!!
Object lookupKey = this.determineCurrentLookupKey(); 得到数据源的key,在第3步我们修改了这个方法
DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey); 通过key在resolvedDataSources中得到一个数据源,而resolvedDataSources在第2步也定义了
if (dataSource == null && (this.lenientFallback || lookupKey == null)) { dataSource = this.resolvedDefaultDataSource; } 如果通过key没有拿到dataSource,就使用默认的数据源,在上边也配置默认数据源了- 如果通过key没有拿到数据源,也没有配置默认数据源,那只能抛出异常了
