若没空探究原理可直接跳转到“实现方式:注解+切面”目录
数据源切换方法
Spring对数据源的管理类似于策略模式,不懂策略模式也没关系,其实就是有一个全局的键值对,类型是
Map<String, DataSource>
。当
JDBC
操作数据库之时,会根据不同的key值选择不同的数据源。而这个key值可以放到方法的注解里。
所以切换数据源的思路就是让
JDBC
在获取数据源时根据key获取到要切换的数据源。
JDBC
提供了
AbstractRoutingDataSource
抽象类,类名意思是数据源路由,该类提供了一个抽象方法
determineCurrentLookupKey()
,切换数据源时
JDBC
会调用这个方法获取数据源的key,所以只需要实现该方法,改变该方法中返回的key值即可。
源码解读
1.从类关系图中可以看出
AbstractRoutingDataSource
类实现了
DataSource
接口,后者有两个
getConnection()
方法,即获取DB连接的作用。
实现了这两个方法AbstractRoutingDataSource
其中
determineTargetDataSource()
方法的作用就是获取实际的数据源,其内部调用了
determineCurrentLookupKey()
方法,取到当前设定的key,通过key在上下文
this.resolvedDataSources
属性中尝试获取DataSource对象,这个对象即当前连接的数据源
3.那么
this.resolvedDataSources
在哪里维护呢? 继续在
AbstractRoutingDataSource
类里找,可以找到
afterPropertiesSet()
方法,这个方法是
InitializingBean
接口的,作用是在bean的所有属性设置完成后便会调用此方法。可以看到
this.resolvedDataSources
是从
this.targetDataSources
取的信息。
所以只需要改变this.targetDataSources,即可改变this.resolvedDataSources;后续改变determineCurrentLookupKey()的返回值(key),在调用getConnection()时即可获取到指定的数据源
实现方式:注解+切面
别看步骤挺多,但其实很容易理解和使用
1.配置文件示例:
spring:
datasource:
master: # 数据源master
jdbc-url: jdbc:mysql://localhost:3306/master?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
db1: # 数据源1
jdbc-url: jdbc:mysql://localhost:3306/db2?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
2.创建数据源配置类
创建数据源配置类(我这里为了方便区分就为每一个数据源创建了一个配置类,当然也可以把所有的数据源配置在一个类里)
@Configuration@EnableConfigurationProperties({MasterDataSourceProperties.class})publicclassMasterDataSourceConfig{/**
* 这个MasterDataSourceProperties是读取配置文件的类,我这里为了省篇幅就不展示了
**/@AutowiredprivateMasterDataSourceProperties masterDataSourceProperties;@Bean@PrimarypublicDataSourcemasterDataSource(){DruidDataSource datasource =newDruidDataSource();
datasource.setUrl(masterDataSourceProperties.getUrl());
datasource.setUsername(masterDataSourceProperties.getUsername());
datasource.setPassword(AESUtil.aesDecode(masterDataSourceProperties.getPassword()));
datasource.setDriverClassName(masterDataSourceProperties.getDriverClassName());......return datasource;}}@Configuration@EnableConfigurationProperties({OdsDataSourceProperties.class})publicclassDB1DataSourceConfig{/**
* 这个DB1DataSourceProperties是读取配置文件的类,我这里为了省篇幅就不展示了
**/@AutowiredprivateDB1DataSourceProperties dB1DataSourceProperties;@BeanpublicDataSourcedb1DataSource(){DruidDataSource datasource =newDruidDataSource();
datasource.setUrl(dB1DataSourceProperties.getUrl());
datasource.setUsername(dB1DataSourceProperties.getUsername());
datasource.setPassword(AESUtil.aesDecode(dB1DataSourceProperties.getPassword()));
datasource.setDriverClassName(dB1DataSourceProperties.getDriverClassName());......return datasource;}}
3.创建
DynamicDataSource
创建自己的一个
DynamicDataSource
类(名字任意)继承
AbstractRoutingDataSource
,维护数据源,提供切换方法。
publicclassDynamicDataSourceextendsAbstractRoutingDataSource{/**
* 如果不希望数据源在启动配置时就加载好,可以定制这个方法,从任何你希望的地方读取并返回数据源
* 比如从数据库、文件、外部接口等读取数据源信息,并最终返回一个DataSource实现类对象即可
*/@OverrideprotectedDataSourcedetermineTargetDataSource(){returnsuper.determineTargetDataSource();}/**
* 如果希望所有数据源在启动配置时就加载好,然后通过设置数据源Key值来切换数据,定制这个方法
*/@OverrideprotectedObjectdetermineCurrentLookupKey(){returnDynamicDataSourceContextHolder.getDataSourceKey();}/**
* 设置默认数据源
*
* @param defaultDataSource
*/publicvoidsetDefaultDataSource(Object defaultDataSource){super.setDefaultTargetDataSource(defaultDataSource);}/**
* 设置数据源
*
* @param dataSources
*/publicvoidsetDataSources(Map<Object,Object> dataSources){super.setTargetDataSources(dataSources);// 将数据源的 key 放到数据源上下文的 key 集合中,用于切换时判断数据源是否有效DynamicDataSourceContextHolder.addDataSourceKeys(dataSources.keySet());}}
4.创建数据源上下文处理器
DynamicDataSourceContextHolder
创建数据源上下文处理器
DynamicDataSourceContextHolder
用以存储当前线程需要使用的数据源名称。
publicclassDynamicDataSourceContextHolder{privatestaticfinalThreadLocal<String> contextHolder =newThreadLocal<String>(){/**
* 将 master 数据源的 key作为默认数据源的 key
*/@OverrideprotectedStringinitialValue(){return"master";}};/**
* 数据源的 key集合,用于切换时判断数据源是否存在
*/publicstaticList<Object> dataSourceKeys =newArrayList<>();/**
* 切换数据源
*
* @param key
*/publicstaticvoidsetDataSourceKey(String key){
contextHolder.set(key);}/**
* 获取数据源
*
* @return
*/publicstaticStringgetDataSourceKey(){return contextHolder.get();}/**
* 重置数据源
*/publicstaticvoidclearDataSourceKey(){
contextHolder.remove();}/**
* 判断是否包含数据源
*
* @param key 数据源key
* @return
*/publicstaticbooleancontainDataSourceKey(String key){return dataSourceKeys.contains(key);}/**
* 添加数据源keys
*
* @param keys
* @return
*/publicstaticbooleanaddDataSourceKeys(Collection<?extendsObject> keys){return dataSourceKeys.addAll(keys);}}
5.创建数据源配置类
DataSourceConfig
创建数据源配置类
DataSourceConfig
,将所有数据源注入到spring容器
@Configuration@EnableAutoConfiguration(exclude ={DataSourceAutoConfiguration.class})// 排除 DataSourceAutoConfiguration 的自动配置,避免环形调用publicclassDataSourceConfig{@AutowiredprivateMasterDataSourceConfig masterDataSourceConfig;@AutowiredprivateDB1DataSourceConfig dB1DataSourceConfig;/**
* 设置动态数据源为主数据源
*
* @return
*/@Bean@PrimarypublicDynamicDataSourcedataSource(){DynamicDataSource dynamicDataSource =newDynamicDataSource();// 默认指定的数据源
dynamicDataSource.setDefaultDataSource(masterDataSourceConfig.masterDataSource());// 将数据源设置进mapMap<Object,Object> dataSourceMap =newHashMap<>(8);
dataSourceMap.put(DataSourceEnum.MASTER.toString(), masterDataSourceConfig.masterDataSource());
dataSourceMap.put(DataSourceEnum.DB1.toString(), dB1DataSourceConfig.db1DataSource());// 使用 Map 保存多个数据源,并设置到动态数据源对象中,这个值最终会在afterPropertiesSet中被设置到resolvedDataSources上
dynamicDataSource.setDataSources(dataSourceMap);return dynamicDataSource;}}
6.创建数据源类型枚举DataSourceEnum
publicenumDataSourceEnum{/**默认类型*/MASTER,/**DB1类型*/DB1,;}
7.创建自定义注解@DataSource
@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceDataSource{/**
* 数据源key值
* @return
*/DataSourceEnumvalue();}
8.创建切面DynamicDataSourceAspect
@Slf4j@Aspect@Order(-1)@ComponentpublicclassDynamicDataSourceAspect{/**
* 切换数据源
*
* @param point
* @param dataSource
*/@Before("@annotation(dataSource))")publicvoidswitchDataSource(JoinPoint point,DataSource dataSource){if(!DynamicDataSourceContextHolder.containDataSourceKey(dataSource.value().toString())){
log.info("DataSource [{}] doesn't exist, use default DataSource", dataSource.value());}else{// 切换数据源DynamicDataSourceContextHolder.setDataSourceKey(dataSource.value().toString());
log.info("Switch DataSource to [{}] in Method [{}]",DynamicDataSourceContextHolder.getDataSourceKey(),
point.getSignature());}}/**
* 重置数据源
*
* @param point
* @param dataSource
*/@After("@annotation(dataSource))")publicvoidrestoreDataSource(JoinPoint point,DataSource dataSource){// 将数据源置为默认数据源DynamicDataSourceContextHolder.clearDataSourceKey();
log.info("Restore DataSource to [{}] in Method [{}]",DynamicDataSourceContextHolder.getDataSourceKey(), point.getSignature());}}
如何使用
@Override@DataSource(DataSourceEnum.DB1)publicPage<AuditTaskDto>queryAuditTask(AuditTaskQuery query){Page<AuditTaskDto> page = baseMapper.queryAuditTask(query);return page;}
如此就可以直接使用自定义的@DataSource注解来切换数据源啦~~~
版权归原作者 凡人编程传 所有, 如有侵权,请联系我们删除。