mybatis-plus多數據源解析
寫在前面文章已收錄到我的Github精選,歡迎Star:https://github.com/yehongzhi/learningSummary
上一篇文章大致介紹了dynamic-datasource的功能,用起來的確很方便,只需要一個@DS注解,加上一些簡單的配置即可完成多數據源的切換。究竟是怎么做到的呢,底層是怎么實現(xiàn)呢?帶著這個疑問,一起研究了一下源碼。
由于框架本身功能點比較多,有很多小功能比如支持spel、正則表達式匹配,動態(tài)增刪數據源這種功能的源碼就不去細講了。我們只關心核心的功能,就是多數據源的切換。
源碼解析首先我們都記得,一開始需要引入spring-boot-starter:
<dependency>
????<groupId>com.baomidougroupId>
????<artifactId>dynamic-datasource-spring-boot-starterartifactId>
????<version>3.3.0version>
dependency>
一般starter自動配置,都是從 META-INF/spring.factories文件中指定自動配置類:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration
接著打開這個類:
/**
?*?動態(tài)數據源核心自動配置類
?*
?*?@author?TaoYu?Kanyuxia
?*?@see?DynamicDataSourceProvider
?*?@see?DynamicDataSourceStrategy
?*?@see?DynamicRoutingDataSource
?*?@since?1.0.0
?*/
@Slf4j
@Configuration
@AllArgsConstructor
//以spring.datasource.dynamic為前綴讀取配置
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
//在SpringBoot注入DataSourceAutoConfiguration的bean自動配置之前,先加載注入當前這個類的bean到容器中
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
//引入了Druid的autoConfig和各種數據源連接池的Creator
@Import(value?=?{DruidDynamicDataSourceConfiguration.class,?DynamicDataSourceCreatorAutoConfiguration.class})
//條件加載,當前綴是"spring.datasource.dynamic"配置的時候啟用這個autoConfig
@ConditionalOnProperty(prefix?=?DynamicDataSourceProperties.PREFIX,?name?=?"enabled",?havingValue?=?"true",?matchIfMissing?=?true)
public?class?DynamicDataSourceAutoConfiguration?{
?
????private?final?DynamicDataSourceProperties?properties;
????//讀取多數據源配置,注入到spring容器中
????@Bean
????@ConditionalOnMissingBean
????public?DynamicDataSourceProvider?dynamicDataSourceProvider()?{
????????Map?datasourceMap?=?properties.getDatasource();
????????return?new?YmlDynamicDataSourceProvider(datasourceMap);
????}
????//注冊自己的動態(tài)多數據源DataSource
????@Bean
????@ConditionalOnMissingBean
????public?DataSource?dataSource(DynamicDataSourceProvider?dynamicDataSourceProvider)?{
????????DynamicRoutingDataSource?dataSource?=?new?DynamicRoutingDataSource();
????????dataSource.setPrimary(properties.getPrimary());
????????dataSource.setStrict(properties.getStrict());
????????dataSource.setStrategy(properties.getStrategy());
????????dataSource.setProvider(dynamicDataSourceProvider);
????????dataSource.setP6spy(properties.getP6spy());
????????dataSource.setSeata(properties.getSeata());
????????return?dataSource;
????}
????//AOP切面,對DS注解過的方法進行增強,達到切換數據源的目的
????@Role(value?=?BeanDefinition.ROLE_INFRASTRUCTURE)
????@Bean
????@ConditionalOnMissingBean
????public?DynamicDataSourceAnnotationAdvisor?dynamicDatasourceAnnotationAdvisor(DsProcessor?dsProcessor)?{
????????DynamicDataSourceAnnotationInterceptor?interceptor?=?new?DynamicDataSourceAnnotationInterceptor(properties.isAllowedPublicOnly(),?dsProcessor);
????????DynamicDataSourceAnnotationAdvisor?advisor?=?new?DynamicDataSourceAnnotationAdvisor(interceptor);
????????advisor.setOrder(properties.getOrder());
????????return?advisor;
????}
????//關于分布式事務加強
????@Role(value?=?BeanDefinition.ROLE_INFRASTRUCTURE)
????@ConditionalOnProperty(prefix?=?DynamicDataSourceProperties.PREFIX,?name?=?"seata",?havingValue?=?"false",?matchIfMissing?=?true)
????@Bean
????public?Advisor?dynamicTransactionAdvisor()?{
????????AspectJExpressionPointcut?pointcut?=?new?AspectJExpressionPointcut();
????????pointcut.setExpression("@annotation(com.baomidou.dynamic.datasource.annotation.DSTransactional)");
????????return?new?DefaultPointcutAdvisor(pointcut,?new?DynamicTransactionAdvisor());
????}
????//動態(tài)參數解析器鏈
????@Bean
????@ConditionalOnMissingBean
????public?DsProcessor?dsProcessor()?{
????????DsHeaderProcessor?headerProcessor?=?new?DsHeaderProcessor();
????????DsSessionProcessor?sessionProcessor?=?new?DsSessionProcessor();
????????DsSpelExpressionProcessor?spelExpressionProcessor?=?new?DsSpelExpressionProcessor();
????????headerProcessor.setNextProcessor(sessionProcessor);
????????sessionProcessor.setNextProcessor(spelExpressionProcessor);
????????return?headerProcessor;
????}
}
我們可以發(fā)現(xiàn),在使用的時候配置的前綴為spring.datasource.dynamic的配置都會被讀取到DynamicDataSourceProperties類,作為一個Bean注入到Spring容器。其實這種讀取配置文件信息的方式在日常開發(fā)中也是很常見的。
@Slf4j
@Getter
@Setter
@ConfigurationProperties(prefix?=?DynamicDataSourceProperties.PREFIX)
public?class?DynamicDataSourceProperties?{
????public?static?final?String?PREFIX?=?"spring.datasource.dynamic";
????public?static?final?String?HEALTH?=?PREFIX?+?".health";
????/**
?????*?必須設置默認的庫,默認master
?????*/
????private?String?primary?=?"master";
????/**
?????*?是否啟用嚴格模式,默認不啟動.?嚴格模式下未匹配到數據源直接報錯,?非嚴格模式下則使用默認數據源primary所設置的數據源
?????*/
????private?Boolean?strict?=?false;
????/**
?????*?是否使用p6spy輸出,默認不輸出
?????*/
????private?Boolean?p6spy?=?false;
????/**
?????*?是否使用開啟seata,默認不開啟
?????*/
????private?Boolean?seata?=?false;
????/**
?????*?seata使用模式,默認AT
?????*/
????private?SeataMode?seataMode?=?SeataMode.AT;
????/**
?????*?是否使用?spring?actuator?監(jiān)控檢查,默認不檢查
?????*/
????private?boolean?health?=?false;
????/**
?????*?每一個數據源
?????*/
????private?Map?datasource?=?new?LinkedHashMap<>();
????/**
?????*?多數據源選擇算法clazz,默認負載均衡算法
?????*/
????private?Class?extends?DynamicDataSourceStrategy>?strategy?=?LoadBalanceDynamicDataSourceStrategy.class;
????/**
?????*?aop切面順序,默認優(yōu)先級最高
?????*/
????private?Integer?order?=?Ordered.HIGHEST_PRECEDENCE;
????/**
?????*?Druid全局參數配置
?????*/
????@NestedConfigurationProperty
????private?DruidConfig?druid?=?new?DruidConfig();
????/**
?????*?HikariCp全局參數配置
?????*/
????@NestedConfigurationProperty
????private?HikariCpConfig?hikari?=?new?HikariCpConfig();
????/**
?????*?全局默認publicKey
?????*/
????private?String?publicKey?=?CryptoUtils.DEFAULT_PUBLIC_KEY_STRING;
????/**
?????*?aop?切面是否只允許切?public?方法
?????*/
????private?boolean?allowedPublicOnly?=?true;
}
但是讀取到配置文件怎么讓這些配置文件信息跟spring的DataSource結合起來呢?我們利用反向思維,從結果往回推,要整合一個數據源到spring,是需要實現(xiàn)DataSource接口,那么Mybatis-Plus的動態(tài)數據源也是有實現(xiàn)的,就是這個:
/**
?*?抽象動態(tài)獲取數據源
?*
?*?@author?TaoYu
?*?@since?2.2.0
?*/
public?abstract?class?AbstractRoutingDataSource?extends?AbstractDataSource?{
????//抽象方法,由子類實現(xiàn),讓子類決定最終使用的數據源
????protected?abstract?DataSource?determineDataSource();
????//重寫getConnection()方法,實現(xiàn)切換數據源的功能
????@Override
????public?Connection?getConnection()?throws?SQLException?{
????????//這里xid涉及分布式事務的處理
????????String?xid?=?TransactionContext.getXID();
????????if?(StringUtils.isEmpty(xid))?{
????????????//不使用分布式事務,就是直接返回一個數據連接
????????????return?determineDataSource().getConnection();
????????}?else?{
????????????String?ds?=?DynamicDataSourceContextHolder.peek();
????????????ConnectionProxy?connection?=?ConnectionFactory.getConnection(ds);
????????????return?connection?==?null???getConnectionProxy(ds,?determineDataSource().getConnection())?:?connection;
????????}
????}
}
上面的源碼如果學過模板模式肯定都熟悉,他把獲取DataSource的行為延伸到子類去實現(xiàn)了,所以關鍵在于看子類的實現(xiàn):
@Slf4j
public?class?DynamicRoutingDataSource?extends?AbstractRoutingDataSource?implements?InitializingBean,?DisposableBean?{
????private?static?final?String?UNDERLINE?=?"_";
????/**
?????*?所有數據庫
?????*/
????private?final?Map?dataSourceMap?=?new?ConcurrentHashMap<>();
????/**
?????*?分組數據庫
?????*/
????private?final?Map?groupDataSources?=?new?ConcurrentHashMap<>();
????@Setter
????private?DynamicDataSourceProvider?provider;
????@Setter
????private?Class?extends?DynamicDataSourceStrategy>?strategy?=?LoadBalanceDynamicDataSourceStrategy.class;
????@Setter
????private?String?primary?=?"master";
????@Setter
????private?Boolean?strict?=?false;
????@Setter
????private?Boolean?p6spy?=?false;
????@Setter
????private?Boolean?seata?=?false;
????@Override
????public?DataSource?determineDataSource()?{
????????return?getDataSource(DynamicDataSourceContextHolder.peek());
????}
????
????private?DataSource?determinePrimaryDataSource()?{
????????log.debug("dynamic-datasource?switch?to?the?primary?datasource");
????????return?groupDataSources.containsKey(primary)???groupDataSources.get(primary).determineDataSource()?:?dataSourceMap.get(primary);
????}
????
????@Override
????public?void?afterPropertiesSet()?throws?Exception?{
????????//?檢查開啟了配置但沒有相關依賴
????????checkEnv();
????????//?添加并分組數據源
????????Map?dataSources?=?provider.loadDataSources();
????????for?(Map.Entry?dsItem?:?dataSources.entrySet())?{
????????????addDataSource(dsItem.getKey(),?dsItem.getValue());
????????}
????????//?檢測默認數據源是否設置
????????if?(groupDataSources.containsKey(primary))?{
????????????log.info("dynamic-datasource?initial?loaded?[{}]?datasource,primary?group?datasource?named?[{}]",?dataSources.size(),?primary);
????????}?else?if?(dataSourceMap.containsKey(primary))?{
????????????log.info("dynamic-datasource?initial?loaded?[{}]?datasource,primary?datasource?named?[{}]",?dataSources.size(),?primary);
????????}?else?{
????????????throw?new?RuntimeException("dynamic-datasource?Please?check?the?setting?of?primary");
????????}
????}
}
他實現(xiàn)了InitializingBean接口,這個接口需要實現(xiàn)afterPropertiesSet()方法,這是一個Bean的生命周期函數,在Bean初始化的時候做一些操作。
這里做的操作就是檢查配置,然后通過調用provider.loadDataSources()方法獲取到關于DataSource的Map集合,Key是數據源的名稱,Value則是DataSource。
@Slf4j
@AllArgsConstructor
public?class?YmlDynamicDataSourceProvider?extends?AbstractDataSourceProvider?{
????/**
?????*?所有數據源
?????*/
????private?final?Map?dataSourcePropertiesMap;
????@Override
????public?Map?loadDataSources()? {
????????//調AbstractDataSourceProvider的createDataSourceMap()方法
????????return?createDataSourceMap(dataSourcePropertiesMap);
????}
}
@Slf4j
public?abstract?class?AbstractDataSourceProvider?implements?DynamicDataSourceProvider?{
????@Autowired
????private?DefaultDataSourceCreator?defaultDataSourceCreator;
????protected?Map?createDataSourceMap(
????????????Map?dataSourcePropertiesMap) ? {
????????Map?dataSourceMap?=?new?HashMap<>(dataSourcePropertiesMap.size()?*?2);
????????for?(Map.Entry?item?:?dataSourcePropertiesMap.entrySet())?{
????????????DataSourceProperty?dataSourceProperty?=?item.getValue();
????????????String?poolName?=?dataSourceProperty.getPoolName();
????????????if?(poolName?==?null?||?"".equals(poolName))?{
????????????????poolName?=?item.getKey();
????????????}
????????????dataSourceProperty.setPoolName(poolName);
????????????dataSourceMap.put(poolName,?defaultDataSourceCreator.createDataSource(dataSourceProperty));
????????}
????????return?dataSourceMap;
????}
}
這里的defaultDataSourceCreator.createDataSource()方法使用到適配器模式。
因為每種配置數據源創(chuàng)建的DataSource實現(xiàn)類都不一定相同的,所以需要根據配置的數據源類型進行具體的DataSource創(chuàng)建。
@Override
public?DataSource?createDataSource(DataSourceProperty?dataSourceProperty,?String?publicKey)?{
????DataSourceCreator?dataSourceCreator?=?null;
????//this.creators是所有適配的DataSourceCreator實現(xiàn)類
????for?(DataSourceCreator?creator?:?this.creators)?{
????????//根據配置匹配對應的dataSourceCreator
????????if?(creator.support(dataSourceProperty))?{
????????????//如果匹配,則使用對應的dataSourceCreator
????????????dataSourceCreator?=?creator;
????????????break;
????????}
????}
????if?(dataSourceCreator?==?null)?{
????????throw?new?IllegalStateException("creator?must?not?be?null,please?check?the?DataSourceCreator");
????}
????//然后再調用createDataSource方法進行創(chuàng)建對應DataSource
????DataSource?dataSource?=?dataSourceCreator.createDataSource(dataSourceProperty,?publicKey);
????this.runScrip(dataSource,?dataSourceProperty);
????return?wrapDataSource(dataSource,?dataSourceProperty);
}
對應的全部實現(xiàn)類是放在creator包下:

我們看其中一個實現(xiàn)類就:
@Data
@AllArgsConstructor
public?class?HikariDataSourceCreator?extends?AbstractDataSourceCreator?implements?DataSourceCreator?{
????private?static?Boolean?hikariExists?=?false;
????static?{
????????try?{
????????????Class.forName(HIKARI_DATASOURCE);
????????????hikariExists?=?true;
????????}?catch?(ClassNotFoundException?ignored)?{
????????}
????}
????private?HikariCpConfig?hikariCpConfig;
????//創(chuàng)建HikariCp數據源
????@Override
????public?DataSource?createDataSource(DataSourceProperty?dataSourceProperty,?String?publicKey)?{
????????if?(StringUtils.isEmpty(dataSourceProperty.getPublicKey()))?{
????????????dataSourceProperty.setPublicKey(publicKey);
????????}
????????HikariConfig?config?=?dataSourceProperty.getHikari().toHikariConfig(hikariCpConfig);
????????config.setUsername(dataSourceProperty.getUsername());
????????config.setPassword(dataSourceProperty.getPassword());
????????config.setJdbcUrl(dataSourceProperty.getUrl());
????????config.setPoolName(dataSourceProperty.getPoolName());
????????String?driverClassName?=?dataSourceProperty.getDriverClassName();
????????if?(!StringUtils.isEmpty(driverClassName))?{
????????????config.setDriverClassName(driverClassName);
????????}
????????return?new?HikariDataSource(config);
????}
?//判斷是否是HikariCp數據源
????@Override
????public?boolean?support(DataSourceProperty?dataSourceProperty)?{
????????Class?extends?DataSource>?type?=?dataSourceProperty.getType();
????????return?(type?==?null?&&?hikariExists)?||?(type?!=?null?&&?HIKARI_DATASOURCE.equals(type.getName()));
????}
}
再回到之前的,當拿到DataSource的Map集合之后,再做什么呢?
接著調addDataSource()方法,這個方法是根據下劃線"_"對數據源進行分組,最后放到groupDataSources成員變量里面。
/**
?????*?新數據源添加到分組
?????*
?????*?@param?ds?????????新數據源的名字
?????*?@param?dataSource?新數據源
?????*/
private?void?addGroupDataSource(String?ds,?DataSource?dataSource)?{
????if?(ds.contains(UNDERLINE))?{
????????String?group?=?ds.split(UNDERLINE)[0];
????????GroupDataSource?groupDataSource?=?groupDataSources.get(group);
????????if?(groupDataSource?==?null)?{
????????????try?{
????????????????//順便設置負載均衡策略,strategy默認是LoadBalanceDynamicDataSourceStrategy
????????????????groupDataSource?=?new?GroupDataSource(group,?strategy.getDeclaredConstructor().newInstance());
????????????????groupDataSources.put(group,?groupDataSource);
????????????}?catch?(Exception?e)?{
????????????????throw?new?RuntimeException("dynamic-datasource?-?add?the?datasource?named?"?+?ds?+?"?error",?e);
????????????}
????????}
????????groupDataSource.addDatasource(ds,?dataSource);
????}
}
分組的時候,會順便把負載均衡策略也一起設置進去。這個負載均衡是做什么呢?
比如一個組master里有三個數據源(A、B、C),需要合理地分配使用的頻率,不可能全都使用某一個,那么這就需要負載均衡策略,默認是輪詢,對應的類是:
public?class?LoadBalanceDynamicDataSourceStrategy?implements?DynamicDataSourceStrategy?{
????/**
?????*?負載均衡計數器
?????*/
????private?final?AtomicInteger?index?=?new?AtomicInteger(0);
????@Override
????public?DataSource?determineDataSource(List?dataSources) ?{
????????return?dataSources.get(Math.abs(index.getAndAdd(1)?%?dataSources.size()));
????}
}
獲取數據源的時候就通過:
@Override
public?DataSource?determineDataSource()?{
????return?getDataSource(DynamicDataSourceContextHolder.peek());
}
/**
?????*?獲取數據源
?????*
?????*?@param?ds?數據源名稱
?????*?@return?數據源
?????*/
public?DataSource?getDataSource(String?ds)?{
????//沒有傳數據源名稱,默認使用主數據源
????if?(StringUtils.isEmpty(ds))?{
????????return?determinePrimaryDataSource();
????//判斷分組數據源是否包含,如果包含則從分組數據源獲取返回
????}?else?if?(!groupDataSources.isEmpty()?&&?groupDataSources.containsKey(ds))?{
????????log.debug("dynamic-datasource?switch?to?the?datasource?named?[{}]",?ds);
????????return?groupDataSources.get(ds).determineDataSource();
????//如果普通數據源包含,則從普通數據源返回
????}?else?if?(dataSourceMap.containsKey(ds))?{
????????log.debug("dynamic-datasource?switch?to?the?datasource?named?[{}]",?ds);
????????return?dataSourceMap.get(ds);
????}
????if?(strict)?{
????????throw?new?RuntimeException("dynamic-datasource?could?not?find?a?datasource?named"?+?ds);
????}
????return?determinePrimaryDataSource();
}
那么上面的DynamicDataSourceContextHolder這個類是干嘛的呢?注解@DS的值又是怎么傳進來的呢?
回到最開始的自動配置類,其中有一個是配置DynamicDataSourceAnnotationAdvisor的,還設置了一個攔截器:
@Role(value?=?BeanDefinition.ROLE_INFRASTRUCTURE)
@Bean
@ConditionalOnMissingBean
public?DynamicDataSourceAnnotationAdvisor?dynamicDatasourceAnnotationAdvisor(DsProcessor?dsProcessor)?{
????//創(chuàng)建攔截器
????DynamicDataSourceAnnotationInterceptor?interceptor?=?new?DynamicDataSourceAnnotationInterceptor(properties.isAllowedPublicOnly(),?dsProcessor);
????DynamicDataSourceAnnotationAdvisor?advisor?=?new?DynamicDataSourceAnnotationAdvisor(interceptor);
????advisor.setOrder(properties.getOrder());
????return?advisor;
}
DynamicDataSourceAnnotationAdvisor是用于AOP切面編程的,針對注解@DS的切面進行處理:
public?class?DynamicDataSourceAnnotationAdvisor?extends?AbstractPointcutAdvisor?implements?BeanFactoryAware?{
????//通知
????private?final?Advice?advice;
????//切入點
????private?final?Pointcut?pointcut;
????public?DynamicDataSourceAnnotationAdvisor(@NonNull?DynamicDataSourceAnnotationInterceptor?dynamicDataSourceAnnotationInterceptor)?{
????????this.advice?=?dynamicDataSourceAnnotationInterceptor;
????????this.pointcut?=?buildPointcut();
????}
????@Override
????public?Pointcut?getPointcut()?{
????????return?this.pointcut;
????}
????@Override
????public?Advice?getAdvice()?{
????????return?this.advice;
????}
????@Override
????public?void?setBeanFactory(BeanFactory?beanFactory)?throws?BeansException?{
????????if?(this.advice?instanceof?BeanFactoryAware)?{
????????????((BeanFactoryAware)?this.advice).setBeanFactory(beanFactory);
????????}
????}
????private?Pointcut?buildPointcut()?{
????????//類上面添加了注解
????????Pointcut?cpc?=?new?AnnotationMatchingPointcut(DS.class,?true);
????????//方法上添加了注解
????????Pointcut?mpc?=?new?AnnotationMethodPoint(DS.class);
????????//方法優(yōu)于類
????????return?new?ComposablePointcut(cpc).union(mpc);
????}
}
切入點我們都清楚了,是@DS注解。那么做了什么處理,主要看advice,也就是傳進來的那個攔截器
DynamicDataSourceAnnotationInterceptor。
public?class?DynamicDataSourceAnnotationInterceptor?implements?MethodInterceptor?{
????/**
?????*?The?identification?of?SPEL.
?????*/
????private?static?final?String?DYNAMIC_PREFIX?=?"#";
????private?final?DataSourceClassResolver?dataSourceClassResolver;
????private?final?DsProcessor?dsProcessor;
????public?DynamicDataSourceAnnotationInterceptor(Boolean?allowedPublicOnly,?DsProcessor?dsProcessor)?{
????????dataSourceClassResolver?=?new?DataSourceClassResolver(allowedPublicOnly);
????????this.dsProcessor?=?dsProcessor;
????}
????@Override
????public?Object?invoke(MethodInvocation?invocation)?throws?Throwable?{
????????//找到@DS注解的屬性值,也就是數據源名稱
????????String?dsKey?=?determineDatasourceKey(invocation);
????????//把數據源名稱push到當前線程的棧
????????DynamicDataSourceContextHolder.push(dsKey);
????????try?{
????????????//執(zhí)行當前方法
????????????return?invocation.proceed();
????????}?finally?{
????????????//從棧里釋放數據源
????????????DynamicDataSourceContextHolder.poll();
????????}
????}
????//這個是使用責任鏈模式進行一些處理,可以先不管他
????private?String?determineDatasourceKey(MethodInvocation?invocation)?{
????????String?key?=?dataSourceClassResolver.findDSKey(invocation.getMethod(),?invocation.getThis());
????????return?(!key.isEmpty()?&&?key.startsWith(DYNAMIC_PREFIX))???dsProcessor.determineDatasource(invocation,?key)?:?key;
????}
}
這里也有一個DynamicDataSourceContextHolder,這樣就跟前面獲取數據連接關聯(lián)起來了,最后我們看一下這個類的源碼:
/**
?*?核心基于ThreadLocal的切換數據源工具類
?*
?*?@author?TaoYu?Kanyuxia
?*?@since?1.0.0
?*/
public?final?class?DynamicDataSourceContextHolder?{
????/**
?????*?為什么要用鏈表存儲(準確的是棧)
?????*?
?????*?為了支持嵌套切換,如ABC三個service都是不同的數據源
?????*?其中A的某個業(yè)務要調B的方法,B的方法需要調用C的方法。一級一級調用切換,形成了鏈。
?????*?傳統(tǒng)的只設置當前線程的方式不能滿足此業(yè)務需求,必須使用棧,后進先出。
?????*?
?????*/
????private?static?final?ThreadLocal>?LOOKUP_KEY_HOLDER?=?new?NamedThreadLocal>("dynamic-datasource")?{
????????@Override
????????protected?Deque?initialValue()? {
????????????return?new?ArrayDeque<>();
????????}
????};
????private?DynamicDataSourceContextHolder()?{
????}
????/**
?????*?獲得當前線程數據源
?????*
?????*?@return?數據源名稱
?????*/
????public?static?String?peek()?{
????????return?LOOKUP_KEY_HOLDER.get().peek();
????}
????/**
?????*?設置當前線程數據源
?????*?
?????*?如非必要不要手動調用,調用后確保最終清除
?????*?
?????*
?????*?@param?ds?數據源名稱
?????*/
????public?static?void?push(String?ds)?{
????????LOOKUP_KEY_HOLDER.get().push(StringUtils.isEmpty(ds)???""?:?ds);
????}
????/**
?????*?清空當前線程數據源
?????*?
?????*?如果當前線程是連續(xù)切換數據源?只會移除掉當前線程的數據源名稱
?????*?
?????*/
????public?static?void?poll()?{
????????Deque?deque?=?LOOKUP_KEY_HOLDER.get();
????????deque.poll();
????????if?(deque.isEmpty())?{
????????????LOOKUP_KEY_HOLDER.remove();
????????}
????}
????/**
?????*?強制清空本地線程
?????*?
?????*?防止內存泄漏,如手動調用了push可調用此方法確保清除
?????*?
?????*/
????public?static?void?clear()?{
????????LOOKUP_KEY_HOLDER.remove();
????}
}
這里為什么使用棧,主要是會存在嵌套切換數據源的情況,也就是最里面那層數據源應該先釋放,最外面那層的數據源應該最后釋放,所以需要用棧的數據結構。
整體流程可能大家還是有點暈,畢竟有點繞,很正常。那么想研究透徹一點,我建議大家自己打開IDEA,參考我寫的去研究一下。這里我畫個整體的流程圖,能有個大概的思路:
總結源碼解析能提高讀代碼的能力,讀代碼的能力我覺得是很重要的,因為當我們加入一個新公司的時候,對項目不熟悉,那么就需要從文檔,代碼上面去了解項目。讀懂代碼才能去修改、擴展。
這篇文章介紹的這個框架的源碼解析只是涉及核心代碼,所以不是很難,有興趣的同學可以自己多看幾遍。多數據源的應用在日常項目中也是很常見的場景。
非常感謝你的閱讀,希望這篇文章能給到你幫助和啟發(fā)。
覺得有用就點個贊吧,你的點贊是我創(chuàng)作的最大動力~
我是一個努力讓大家記住的程序員。我們下期再見!!!
能力有限,如果有什么錯誤或者不當之處,請大家批評指正,一起學習交流!
