SpringBoot配置動態(tài)數(shù)據(jù)源的實戰(zhàn)詳解
數(shù)據(jù)源切換方法
Spring對數(shù)據(jù)源的管理類似于策略模式,不懂策略模式也沒關(guān)系,其實就是有一個全局的鍵值對,類型是Map<String, DataSource>。當(dāng)JDBC操作數(shù)據(jù)庫之時,會根據(jù)不同的key值選擇不同的數(shù)據(jù)源。而這個key值可以放到方法的注解里。
所以切換數(shù)據(jù)源的思路就是讓JDBC在獲取數(shù)據(jù)源時根據(jù)key獲取到要切換的數(shù)據(jù)源。
JDBC提供了AbstractRoutingDataSource抽象類,類名意思是數(shù)據(jù)源路由,該類提供了一個抽象方法determineCurrentLookupKey(),切換數(shù)據(jù)源時JDBC會調(diào)用這個方法獲取數(shù)據(jù)源的key,所以只需要實現(xiàn)該方法,改變該方法中返回的key值即可。
源碼解讀
1.從類關(guān)系圖中可以看出AbstractRoutingDataSource類實現(xiàn)了DataSource接口,后者有兩個getConnection()方法,即獲取DB連接的作用。

2.AbstractRoutingDataSource實現(xiàn)了這兩個方法

其中determineTargetDataSource()方法的作用就是獲取實際的數(shù)據(jù)源,其內(nèi)部調(diào)用了determineCurrentLookupKey()方法,取到當(dāng)前設(shè)定的key,通過key在上下文this.resolvedDataSources屬性中嘗試獲取DataSource對象,這個對象即當(dāng)前連接的數(shù)據(jù)源

3.那么this.resolvedDataSources在哪里維護呢? 繼續(xù)在AbstractRoutingDataSource類里找,可以找到afterPropertiesSet()方法,這個方法是InitializingBean接口的,作用是在bean的所有屬性設(shè)置完成后便會調(diào)用此方法??梢钥吹?code>this.resolvedDataSources是從this.targetDataSources取的信息。

所以只需要改變this.targetDataSources,即可改變this.resolvedDataSources;后續(xù)改變determineCurrentLookupKey()的返回值(key),在調(diào)用getConnection()時即可獲取到指定的數(shù)據(jù)源
實現(xiàn)方式:注解+切面
別看步驟挺多,但其實很容易理解和使用
1.配置文件示例:
spring:
datasource:
master: # 數(shù)據(jù)源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: # 數(shù)據(jù)源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.創(chuàng)建數(shù)據(jù)源配置類
創(chuàng)建數(shù)據(jù)源配置類(我這里為了方便區(qū)分就為每一個數(shù)據(jù)源創(chuàng)建了一個配置類,當(dāng)然也可以把所有的數(shù)據(jù)源配置在一個類里)
@Configuration
@EnableConfigurationProperties({MasterDataSourceProperties.class})
public class MasterDataSourceConfig {
/**
* 這個MasterDataSourceProperties是讀取配置文件的類,我這里為了省篇幅就不展示了
**/
@Autowired
private MasterDataSourceProperties masterDataSourceProperties;
@Bean
@Primary
public DataSource masterDataSource() {
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(masterDataSourceProperties.getUrl());
datasource.setUsername(masterDataSourceProperties.getUsername());
datasource.setPassword(AESUtil.aesDecode(masterDataSourceProperties.getPassword()));
datasource.setDriverClassName(masterDataSourceProperties.getDriverClassName());
......
return datasource;
}
}
@Configuration
@EnableConfigurationProperties({OdsDataSourceProperties.class})
public class DB1DataSourceConfig {
/**
* 這個DB1DataSourceProperties是讀取配置文件的類,我這里為了省篇幅就不展示了
**/
@Autowired
private DB1DataSourceProperties dB1DataSourceProperties;
@Bean
public DataSource db1DataSource() {
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(dB1DataSourceProperties.getUrl());
datasource.setUsername(dB1DataSourceProperties.getUsername());
datasource.setPassword(AESUtil.aesDecode(dB1DataSourceProperties.getPassword()));
datasource.setDriverClassName(dB1DataSourceProperties.getDriverClassName());
......
return datasource;
}
}
3.創(chuàng)建DynamicDataSource
創(chuàng)建自己的一個DynamicDataSource類(名字任意)繼承AbstractRoutingDataSource,維護數(shù)據(jù)源,提供切換方法。
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 如果不希望數(shù)據(jù)源在啟動配置時就加載好,可以定制這個方法,從任何你希望的地方讀取并返回數(shù)據(jù)源
* 比如從數(shù)據(jù)庫、文件、外部接口等讀取數(shù)據(jù)源信息,并最終返回一個DataSource實現(xiàn)類對象即可
*/
@Override
protected DataSource determineTargetDataSource() {
return super.determineTargetDataSource();
}
/**
* 如果希望所有數(shù)據(jù)源在啟動配置時就加載好,然后通過設(shè)置數(shù)據(jù)源Key值來切換數(shù)據(jù),定制這個方法
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceKey();
}
/**
* 設(shè)置默認數(shù)據(jù)源
*
* @param defaultDataSource
*/
public void setDefaultDataSource(Object defaultDataSource) {
super.setDefaultTargetDataSource(defaultDataSource);
}
/**
* 設(shè)置數(shù)據(jù)源
*
* @param dataSources
*/
public void setDataSources(Map<Object, Object> dataSources) {
super.setTargetDataSources(dataSources);
// 將數(shù)據(jù)源的 key 放到數(shù)據(jù)源上下文的 key 集合中,用于切換時判斷數(shù)據(jù)源是否有效
DynamicDataSourceContextHolder.addDataSourceKeys(dataSources.keySet());
}
}
4.創(chuàng)建數(shù)據(jù)源上下文處理器DynamicDataSourceContextHolder
創(chuàng)建數(shù)據(jù)源上下文處理器DynamicDataSourceContextHolder用以存儲當(dāng)前線程需要使用的數(shù)據(jù)源名稱。
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
/**
* 將 master 數(shù)據(jù)源的 key作為默認數(shù)據(jù)源的 key
*/
@Override
protected String initialValue() {
return "master";
}
};
/**
* 數(shù)據(jù)源的 key集合,用于切換時判斷數(shù)據(jù)源是否存在
*/
public static List<Object> dataSourceKeys = new ArrayList<>();
/**
* 切換數(shù)據(jù)源
*
* @param key
*/
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
/**
* 獲取數(shù)據(jù)源
*
* @return
*/
public static String getDataSourceKey() {
return contextHolder.get();
}
/**
* 重置數(shù)據(jù)源
*/
public static void clearDataSourceKey() {
contextHolder.remove();
}
/**
* 判斷是否包含數(shù)據(jù)源
*
* @param key 數(shù)據(jù)源key
* @return
*/
public static boolean containDataSourceKey(String key) {
return dataSourceKeys.contains(key);
}
/**
* 添加數(shù)據(jù)源keys
*
* @param keys
* @return
*/
public static boolean addDataSourceKeys(Collection<? extends Object> keys) {
return dataSourceKeys.addAll(keys);
}
}
5.創(chuàng)建數(shù)據(jù)源配置類DataSourceConfig
創(chuàng)建數(shù)據(jù)源配置類DataSourceConfig,將所有數(shù)據(jù)源注入到spring容器
@Configuration
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class }) // 排除 DataSourceAutoConfiguration 的自動配置,避免環(huán)形調(diào)用
public class DataSourceConfig {
@Autowired
private MasterDataSourceConfig masterDataSourceConfig;
@Autowired
private DB1DataSourceConfig dB1DataSourceConfig;
/**
* 設(shè)置動態(tài)數(shù)據(jù)源為主數(shù)據(jù)源
*
* @return
*/
@Bean
@Primary
public DynamicDataSource dataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 默認指定的數(shù)據(jù)源
dynamicDataSource.setDefaultDataSource(masterDataSourceConfig.masterDataSource());
// 將數(shù)據(jù)源設(shè)置進map
Map<Object, Object> dataSourceMap = new HashMap<>(8);
dataSourceMap.put(DataSourceEnum.MASTER.toString(), masterDataSourceConfig.masterDataSource());
dataSourceMap.put(DataSourceEnum.DB1.toString(), dB1DataSourceConfig.db1DataSource());
// 使用 Map 保存多個數(shù)據(jù)源,并設(shè)置到動態(tài)數(shù)據(jù)源對象中,這個值最終會在afterPropertiesSet中被設(shè)置到resolvedDataSources上
dynamicDataSource.setDataSources(dataSourceMap);
return dynamicDataSource;
}
}
6.創(chuàng)建數(shù)據(jù)源類型枚舉DataSourceEnum
public enum DataSourceEnum {
/**默認類型*/
MASTER,
/**DB1類型*/
DB1,
;
}
7.創(chuàng)建自定義注解@DataSource
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
/**
* 數(shù)據(jù)源key值
* @return
*/
DataSourceEnum value();
}
8.創(chuàng)建切面DynamicDataSourceAspect
@Slf4j
@Aspect
@Order(-1)
@Component
public class DynamicDataSourceAspect {
/**
* 切換數(shù)據(jù)源
*
* @param point
* @param dataSource
*/
@Before("@annotation(dataSource))")
public void switchDataSource(JoinPoint point, DataSource dataSource) {
if (!DynamicDataSourceContextHolder.containDataSourceKey(dataSource.value().toString())) {
log.info("DataSource [{}] doesn't exist, use default DataSource", dataSource.value());
} else {
// 切換數(shù)據(jù)源
DynamicDataSourceContextHolder.setDataSourceKey(dataSource.value().toString());
log.info("Switch DataSource to [{}] in Method [{}]", DynamicDataSourceContextHolder.getDataSourceKey(),
point.getSignature());
}
}
/**
* 重置數(shù)據(jù)源
*
* @param point
* @param dataSource
*/
@After("@annotation(dataSource))")
public void restoreDataSource(JoinPoint point, DataSource dataSource) {
// 將數(shù)據(jù)源置為默認數(shù)據(jù)源
DynamicDataSourceContextHolder.clearDataSourceKey();
log.info("Restore DataSource to [{}] in Method [{}]", DynamicDataSourceContextHolder.getDataSourceKey(), point.getSignature());
}
}
如何使用
@Override
@DataSource(DataSourceEnum.DB1)
public Page<AuditTaskDto> queryAuditTask(AuditTaskQuery query) {
Page<AuditTaskDto> page = baseMapper.queryAuditTask(query);
return page;
}
如此就可以直接使用自定義的@DataSource注解來切換數(shù)據(jù)源啦~~~
以上就是SpringBoot配置動態(tài)數(shù)據(jù)源的實戰(zhàn)詳解的詳細內(nèi)容,更多關(guān)于SpringBoot動態(tài)數(shù)據(jù)源的資料請關(guān)注腳本之家其它相關(guān)文章!
- SpringBoot中動態(tài)數(shù)據(jù)源配置與使用詳解
- SpringBoot自定義動態(tài)數(shù)據(jù)源的流程步驟
- SpringBoot實現(xiàn)動態(tài)數(shù)據(jù)源切換的項目實踐
- SpringBoot動態(tài)數(shù)據(jù)源連接測試的操作詳解
- SpringBoot實現(xiàn)動態(tài)數(shù)據(jù)源切換的方法總結(jié)
- springboot配置多數(shù)據(jù)源(靜態(tài)和動態(tài)數(shù)據(jù)源)
- SpringBoot中動態(tài)數(shù)據(jù)源是實現(xiàn)與用途
- springboot 動態(tài)數(shù)據(jù)源的實現(xiàn)方法(Mybatis+Druid)
- springboot動態(tài)數(shù)據(jù)源+分布式事務(wù)的實現(xiàn)
相關(guān)文章
SpringCloud hystrix服務(wù)降級學(xué)習(xí)筆記
什么是服務(wù)降級?當(dāng)服務(wù)器壓力劇增的情況下,根據(jù)實際業(yè)務(wù)情況及流量,對一些服務(wù)和頁面有策略的不處理或換種簡單的方式處理,從而釋放服務(wù)器資源以保證核心交易正常運作或高效運作2022-10-10
如何解決Project SDK is not defined問題
這篇文章主要介紹了如何解決Project SDK is not defined問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09
springboot內(nèi)置tomcat之NIO處理流程一覽
這篇文章主要介紹了springboot內(nèi)置tomcat之NIO處理流程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
基于Java的度分秒坐標(biāo)轉(zhuǎn)純經(jīng)緯度坐標(biāo)的漂亮國基地信息管理的方法
本文以java語言為例,詳細介紹如何管理漂亮國的基地信息,為下一步全球的空間可視化打下堅實的基礎(chǔ),首先介紹如何對數(shù)據(jù)進行去重處理,然后介紹在java當(dāng)中如何進行度分秒位置的轉(zhuǎn)換,最后結(jié)合實現(xiàn)原型進行詳細的說明,感興趣的朋友跟隨小編一起看看吧2024-06-06

