SpringBoot配置動(dòng)態(tài)數(shù)據(jù)源的實(shí)戰(zhàn)詳解
數(shù)據(jù)源切換方法
Spring對(duì)數(shù)據(jù)源的管理類似于策略模式,不懂策略模式也沒關(guān)系,其實(shí)就是有一個(gè)全局的鍵值對(duì),類型是Map<String, DataSource>
。當(dāng)JDBC
操作數(shù)據(jù)庫(kù)之時(shí),會(huì)根據(jù)不同的key值選擇不同的數(shù)據(jù)源。而這個(gè)key值可以放到方法的注解里。
所以切換數(shù)據(jù)源的思路就是讓JDBC
在獲取數(shù)據(jù)源時(shí)根據(jù)key獲取到要切換的數(shù)據(jù)源。
JDBC
提供了AbstractRoutingDataSource
抽象類,類名意思是數(shù)據(jù)源路由,該類提供了一個(gè)抽象方法determineCurrentLookupKey()
,切換數(shù)據(jù)源時(shí)JDBC
會(huì)調(diào)用這個(gè)方法獲取數(shù)據(jù)源的key,所以只需要實(shí)現(xiàn)該方法,改變?cè)摲椒ㄖ蟹祷氐膋ey值即可。
源碼解讀
1.從類關(guān)系圖中可以看出AbstractRoutingDataSource
類實(shí)現(xiàn)了DataSource
接口,后者有兩個(gè)getConnection()
方法,即獲取DB連接的作用。
2.AbstractRoutingDataSource
實(shí)現(xiàn)了這兩個(gè)方法
其中determineTargetDataSource()
方法的作用就是獲取實(shí)際的數(shù)據(jù)源,其內(nèi)部調(diào)用了determineCurrentLookupKey()
方法,取到當(dāng)前設(shè)定的key,通過(guò)key在上下文this.resolvedDataSources
屬性中嘗試獲取DataSource對(duì)象,這個(gè)對(duì)象即當(dāng)前連接的數(shù)據(jù)源
3.那么this.resolvedDataSources
在哪里維護(hù)呢? 繼續(xù)在AbstractRoutingDataSource
類里找,可以找到afterPropertiesSet()
方法,這個(gè)方法是InitializingBean
接口的,作用是在bean的所有屬性設(shè)置完成后便會(huì)調(diào)用此方法。可以看到this.resolvedDataSources
是從this.targetDataSources
取的信息。
所以只需要改變this.targetDataSources,即可改變this.resolvedDataSources;后續(xù)改變determineCurrentLookupKey()的返回值(key),在調(diào)用getConnection()時(shí)即可獲取到指定的數(shù)據(jù)源
實(shí)現(xiàn)方式:注解+切面
別看步驟挺多,但其實(shí)很容易理解和使用
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ū)分就為每一個(gè)數(shù)據(jù)源創(chuàng)建了一個(gè)配置類,當(dāng)然也可以把所有的數(shù)據(jù)源配置在一個(gè)類里)
@Configuration @EnableConfigurationProperties({MasterDataSourceProperties.class}) public class MasterDataSourceConfig { /** * 這個(gè)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 { /** * 這個(gè)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)建自己的一個(gè)DynamicDataSource
類(名字任意)繼承AbstractRoutingDataSource
,維護(hù)數(shù)據(jù)源,提供切換方法。
public class DynamicDataSource extends AbstractRoutingDataSource { /** * 如果不希望數(shù)據(jù)源在啟動(dòng)配置時(shí)就加載好,可以定制這個(gè)方法,從任何你希望的地方讀取并返回?cái)?shù)據(jù)源 * 比如從數(shù)據(jù)庫(kù)、文件、外部接口等讀取數(shù)據(jù)源信息,并最終返回一個(gè)DataSource實(shí)現(xiàn)類對(duì)象即可 */ @Override protected DataSource determineTargetDataSource() { return super.determineTargetDataSource(); } /** * 如果希望所有數(shù)據(jù)源在啟動(dòng)配置時(shí)就加載好,然后通過(guò)設(shè)置數(shù)據(jù)源Key值來(lái)切換數(shù)據(jù),定制這個(gè)方法 */ @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceKey(); } /** * 設(shè)置默認(rèn)數(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í)判斷數(shù)據(jù)源是否有效 DynamicDataSourceContextHolder.addDataSourceKeys(dataSources.keySet()); } }
4.創(chuàng)建數(shù)據(jù)源上下文處理器DynamicDataSourceContextHolder
創(chuàng)建數(shù)據(jù)源上下文處理器DynamicDataSourceContextHolder
用以存儲(chǔ)當(dāng)前線程需要使用的數(shù)據(jù)源名稱。
public class DynamicDataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() { /** * 將 master 數(shù)據(jù)源的 key作為默認(rèn)數(shù)據(jù)源的 key */ @Override protected String initialValue() { return "master"; } }; /** * 數(shù)據(jù)源的 key集合,用于切換時(shí)判斷數(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 的自動(dòng)配置,避免環(huán)形調(diào)用 public class DataSourceConfig { @Autowired private MasterDataSourceConfig masterDataSourceConfig; @Autowired private DB1DataSourceConfig dB1DataSourceConfig; /** * 設(shè)置動(dòng)態(tài)數(shù)據(jù)源為主數(shù)據(jù)源 * * @return */ @Bean @Primary public DynamicDataSource dataSource() { DynamicDataSource dynamicDataSource = new DynamicDataSource(); // 默認(rèn)指定的數(shù)據(jù)源 dynamicDataSource.setDefaultDataSource(masterDataSourceConfig.masterDataSource()); // 將數(shù)據(jù)源設(shè)置進(jìn)map Map<Object, Object> dataSourceMap = new HashMap<>(8); dataSourceMap.put(DataSourceEnum.MASTER.toString(), masterDataSourceConfig.masterDataSource()); dataSourceMap.put(DataSourceEnum.DB1.toString(), dB1DataSourceConfig.db1DataSource()); // 使用 Map 保存多個(gè)數(shù)據(jù)源,并設(shè)置到動(dòng)態(tài)數(shù)據(jù)源對(duì)象中,這個(gè)值最終會(huì)在afterPropertiesSet中被設(shè)置到resolvedDataSources上 dynamicDataSource.setDataSources(dataSourceMap); return dynamicDataSource; } }
6.創(chuàng)建數(shù)據(jù)源類型枚舉DataSourceEnum
public enum DataSourceEnum { /**默認(rèn)類型*/ 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ù)源置為默認(rèn)數(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注解來(lái)切換數(shù)據(jù)源啦~~~
以上就是SpringBoot配置動(dòng)態(tài)數(shù)據(jù)源的實(shí)戰(zhàn)詳解的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot動(dòng)態(tài)數(shù)據(jù)源的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用springBoot中的info等級(jí)通過(guò)druid打印sql
這篇文章主要介紹了使用springBoot中的info等級(jí)通過(guò)druid打印sql,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09SpringBoot實(shí)現(xiàn)類似鉤子函數(shù)的方法
這篇文章主要給大家介紹了關(guān)于SpringBoot實(shí)現(xiàn)類似鉤子函數(shù)的方法,文中通過(guò)代碼示例介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-04-04springboot jasypt2.x與jasypt3.x的使用方式
在軟件開發(fā)中,將配置文件中的敏感信息(如數(shù)據(jù)庫(kù)密碼)進(jìn)行加密是保障安全的有效手段,jasypt框架提供了這一功能,支持通過(guò)加密工具類或命令行工具生成密文,并通過(guò)修改配置文件和啟動(dòng)參數(shù)的方式使用密文和密鑰,這樣即便配置文件被泄露2024-09-09MyBatis-Plus中的邏輯刪除功能及實(shí)例分析
本文將詳細(xì)講解MyBatis-Plus中的邏輯刪除特性,并結(jié)合實(shí)際案例進(jìn)行演示和說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03Mybatis配置之typeAlias標(biāo)簽的用法
這篇文章主要介紹了Mybatis配置之typeAlias標(biāo)簽的用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Java游戲服務(wù)器之?dāng)?shù)據(jù)庫(kù)表存取封裝
這篇文章主要介紹了Java游戲服務(wù)器之?dāng)?shù)據(jù)庫(kù)表存取封裝的相關(guān)資料,需要的朋友可以參考下2015-11-11springboot如何使用thymeleaf模板訪問(wèn)html頁(yè)面
springboot中推薦使用thymeleaf模板,使用html作為頁(yè)面展示。那么如何通過(guò)Controller來(lái)訪問(wèn)來(lái)訪問(wèn)html頁(yè)面呢?下面通過(guò)本文給大家詳細(xì)介紹,感興趣的朋友跟隨腳本之家小編一起看看吧2018-05-05