spring的动态数据源AbstractRoutingDataSource
本质用的是多态和本地线程栈,适用于老项目动态切换数据源业务,单体服务
使用场景:SaaS服务部署,单服务多租户。当一个系统中需要多个数据库参与,我公司的业务是每个公司一个数据库,多个公司用同一个域名同一个网站,用的公司名(公司号)登录时
1、实现效果
登录时候输入租户 用户名 密码
登录后显示具体租户信息,后续所有数据库操作都会去租户号的数据库查询
2、实现和继承关系
自定义DynamicDataSource类继承AbstractRoutingDataSource类
AbstractRoutingDataSource类继承了DataSource接口
3、AbstractRoutingDataSource主体逻辑
1、AbstractRoutingDataSource类持有多数据源map,默认数据源key。
2、web项目启动,bean初始化时会从配置文件中读取并转换获取数据源map,以及配置文件中读取的默认数据源key。
3、线程执行时,使用本地线程栈,通过拦截器或手动的方式存入数据源key,在后续sql执行时获取业务需要数据源key。
4、在运行sql时会调用父类getConnection()方法,此方法中会通过子类重写的determineCurrentLookupKey方法决定业务需要数据源key,再从数据源map中获取需要的数据源
注意:同一个线程可以运行,如果开新的线程都需要手动存入本地线程栈
4、项目使用的详细逻辑
1、DynamicDataSource动态数据源配置类的父类AbstractRoutingDataSource
2、AbstractRoutingDataSource的afterPropertiesSet方法,获取targetDataSources属性,由setTargetDataSources注入,在配置文件中指定,完成map初始化。
3、线程执行时,需求通过拦截器或手动的方式存入数据源key到本地线程栈
4、sql执行会调用DynamicDataSource父类getConnection方法,执行调用子类重写的determineTargetDataSource决定数据源key,子类会从本地线程栈中获业务指定数据源key(公司号)
ps:看明白的小伙伴后面可以不看略过了
5、通过三个问题继续分析讲解其原理
AbstractRoutingDataSource类如何影响正在执行mybatis执行sql语句的数据源选择?
AbstractRoutingDataSource类如何初始化所有数据源?
线程执行时候如何选择正确的数据源?
5.1 如何影响正在执行mybatis执行sql语句的数据源选择?
DataSource会绑定到SqlSessionFactoryBean的dataSource对象中
然后在每个sql执行时,调用dataSource接口的getConnection()方法获取数据库的连接,实际执行是会使用AbstractRoutingDataSource类的getConnection()选取指定数据的连接,这就是项目中每个执行sql可以使用AbstractRoutingDataSource类中指定数据源的原因。
那如何存储数据源和线程执行时选择正确的数据源呢?
5.2 如何初始化所有数据源?
AbstractRoutingDataSource类由配置文件注册到spring容器中
AbstractRoutingDataSource类本身有个四个核心属性
//用来通过配置文件指定所有的key值和value数据源
private Map<Object, Object> targetDataSources;
//用来通过配置文件指定默认数据源
private Object defaultTargetDataSource;
//在afterPropertiesSet方法将defaultTargetDataSource中转为resolvedDefaultDataSource,这个是后面存储默认数据源
private DataSource resolvedDefaultDataSource;
//在afterPropertiesSet方法将targetDataSources中转为resolvedDataSources,这个是后面存储使用的名称-数据源映射
private Map<Object, DataSource> resolvedDataSources;
核心方法
afterPropertiesSet
AbstractRoutingDataSource类实现了InitializingBean接口,所以此方法在这个bean初始化时执行afterPropertiesSet方法。afterPropertiesSet方法将配置文件中注入的targetDataSources和defaultTargetDataSource 转换为了后续可以使用的resolvedDefaultDataSource(默认数据源)和resolvedDataSources(存储使用的名称-数据源映射)
至此存储数据源完成,通过配置文件指定的方式,将默认数据源(没有指定数据源)和所有数据源存储的map初始化。剩下一个问题,选择正确的数据源呢?
5.3 线程执行时候如何选择正确的数据源?
在AbstractRoutingDataSource类的超类接口DataSource有核心方法getConnection,sql连接操作时会调用这个方法获取连接
这getConnection方法调用时的核心方法determineTargetDataSource
核心方法是determineCurrentLookupKey(),通过这个方法调用获取业务指定的的map中key值,获取resolvedDataSources中指定key的数据源,此处的key就是业务中指定的公司号
determineCurrentLookupKey()的实现在自定义的子类方法中
自定义的子类方法中,将key值业务定义使用的是本地线程栈技术
运用本地线程栈,可以在线程中手动或者自动(拦截器)将key值设定到本地线程栈对象contextHolder中,当sql执行时会,对调用AbstractRoutingDataSource的getConnection(),再调用determineTargetDataSource(),子类中determineCurrentLookupKey()最终决定了从本地线程栈对象contextHolder获取当前公司key字符串,用此公司key字符串在Map名称-数据源映射resolvedDataSources获取数据源
6、扩展
方法调用(接口调用)新增数据源/删除数据源
新增数据源,添加到map对象targetDataSources中,并重新执行afterPropertiesSet方法
删除数据源,DruidDataSourceStatManager类删除数据源对象,删除动态数据源map指定key并重新执行父类afterPropertiesSet方法
版权归原作者 飞行的小恐龙 所有, 如有侵权,请联系我们删除。