Springboot動(dòng)態(tài)切換數(shù)據(jù)源的具體實(shí)現(xiàn)與原理分析
前言
在springboot項(xiàng)目中只需一句代碼即可實(shí)現(xiàn)多個(gè)數(shù)據(jù)源之間的切換:
// 切換sqlserver數(shù)據(jù)源: DataSourceContextHolder.setDataBaseType(DataSourceEnum.SQLSERVER_DATASOURCE); ...... // 切換mysql數(shù)據(jù)源 DataSourceContextHolder.setDataBaseType(DataSourceEnum.MYSQL_DATASOURCE);
具體實(shí)現(xiàn):
本實(shí)例基于springboot2.5+版本實(shí)現(xiàn)。
1.配置數(shù)據(jù)源:
在配置文件中配置多個(gè)數(shù)據(jù)源的連接信息,用不同的前綴作為區(qū)別:
# sqlserver數(shù)據(jù)源1:前綴為:spring.datasource.sqlserver spring.datasource.sqlserver.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver spring.datasource.sqlserver.jdbc-url=jdbc:sqlserver://localhost:1433;DatabaseName=test spring.datasource.sqlserver.username=sa spring.datasource.sqlserver.password=sa # mysql數(shù)據(jù)源1:前綴為:spring.datasource.mysql spring.datasource.mysql.driver-class-name=com.mysql.jdbc.Driver spring.datasource.mysql.jdbc-url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true spring.datasource.mysql.username=root spring.datasource.mysql.password=root # sqlLite數(shù)據(jù)源1:前綴為:spring.datasource.sqlite spring.datasource.sqlite.driver-class-name=org.sqlite.JDBC spring.datasource.sqlite.jdbc-url=jdbc:sqlite:D://sqllite//test.db spring.datasource.sqlite.username= spring.datasource.sqlite.password= # sqlserver數(shù)據(jù)源2:前綴為:spring.datasource.sqlserver2 spring.datasource.sqlserver2.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver spring.datasource.sqlserver2.jdbc-url=jdbc:sqlserver://localhost;DatabaseName=test1 spring.datasource.sqlserver2.username=sa spring.datasource.sqlserver2.password=sa # 配置數(shù)據(jù)庫(kù)連接池信息 spring.datasource.hikari.maximum-pool-size=32 spring.datasource.hikari.minimum-idle=16
2.新建枚舉類DataSourceEnum,有幾個(gè)數(shù)據(jù)源對(duì)應(yīng)設(shè)置幾個(gè)枚舉類。
public enum DataSourceEnum {
MYSQL_DATASOURCE,
SQLSERVER_DATASOURCE,
SQLSERVER2_DATASOURCE,
SQLLITE_DATASOURCE
}
3.新建數(shù)據(jù)庫(kù)切換工具類DataSourceContextHolder,這里通過ThreadLocal類型的變量來存儲(chǔ)當(dāng)前數(shù)據(jù)源枚舉類,同時(shí)能夠保證線程安全。
public class DataSourceContextHolder {
/**
* 通過ThreadLocal保證線程安全
*/
private static final ThreadLocal<DataSourceEnum> contextHolder = new ThreadLocal<>();
/**
* 設(shè)置數(shù)據(jù)源變量
* @param dataSourceEnum 數(shù)據(jù)源變量
*/
public static void setDataBaseType(DataSourceEnum dataSourceEnum) {
System.out.println("修改數(shù)據(jù)源為:" + dataSourceEnum);
contextHolder.set(dataSourceEnum);
}
/**
* 獲取數(shù)據(jù)源變量
* @return 數(shù)據(jù)源變量
*/
public static DataSourceEnum getDataBaseType() {
DataSourceEnum dataSourceEnum = contextHolder.get() == null ? DataSourceEnum.MYSQL_DATASOURCE : contextHolder.get();
System.out.println("當(dāng)前數(shù)據(jù)源的類型為:" + dataSourceEnum);
return dataSourceEnum;
}
/**
* 清空數(shù)據(jù)類型
*/
public static void clearDataBaseType() {
contextHolder.remove();
}
4.新建DynamicDataSource類繼承AbstractRoutingDataSource類,并實(shí)現(xiàn)determineCurrentLookupKey方法,該方法是指定當(dāng)前默認(rèn)數(shù)據(jù)源的方法。
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataBaseType();
}
}
這個(gè)類看似內(nèi)容不多,但其實(shí)繼承了AbstractRoutingDataSource類是實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源的關(guān)鍵。
5.新建DataSourceConfig類用來創(chuàng)建bean的實(shí)例,其中包括各數(shù)據(jù)源的DataSource實(shí)例,DynamicDataSource實(shí)例以及跟Mybatis相關(guān)的SqlSessionFactory或Spring的JdbcTemplate實(shí)例。
@Configuration
public class DataSourceConfig {
@Bean(name = "sqlserverDataSource")
@ConfigurationProperties(prefix = "spring.datasource.sqlserver")
public DataSource getDateSource1() {
return DataSourceBuilder.create().build();
}
@Bean(name = "sqlserver2DataSource")
@ConfigurationProperties(prefix = "spring.datasource.sqlserver2")
public DataSource getDateSource11() {
return DataSourceBuilder.create().build();
}
@Bean(name = "mysqlDataSource")
@ConfigurationProperties(prefix = "spring.datasource.mysql")
public DataSource getDateSource2() {
return DataSourceBuilder.create().build();
}
@Bean(name = "sqlLiteDataSource")
@ConfigurationProperties(prefix = "spring.datasource.sqlite")
public DataSource getDateSource3() {
return DataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
public DynamicDataSource DataSource(@Qualifier("sqlserverDataSource") DataSource sqlserverDataSource,
@Qualifier("sqlserver2DataSource") DataSource sqlserver2DataSource,
@Qualifier("mysqlDataSource") DataSource mysqlDataSource,
@Qualifier("sqlLiteDataSource") DataSource sqlLiteDataSource) {
//配置多數(shù)據(jù)源
Map<Object, Object> targetDataSource = new HashMap<>();
targetDataSource.put(DataSourceEnum.SQLSERVER_DATASOURCE, sqlserverDataSource);
targetDataSource.put(DataSourceEnum.MYSQL_DATASOURCE, mysqlDataSource);
targetDataSource.put(DataSourceEnum.SQLLITE_DATASOURCE, sqlLiteDataSource);
targetDataSource.put(DataSourceEnum.SQLSERVER2_DATASOURCE, sqlserver2DataSource);
DynamicDataSource dataSource = new DynamicDataSource();
//多數(shù)據(jù)源
dataSource.setTargetDataSources(targetDataSource);
//默認(rèn)數(shù)據(jù)源
dataSource.setDefaultTargetDataSource(sqlserverDataSource);
return dataSource;
}
@Bean(name = "SqlSessionFactory")
public SqlSessionFactory test1SqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dynamicDataSource);
return bean.getObject();
}
@Bean(name = "JdbcTemplate")
public JdbcTemplate test1JdbcTemplate(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) {
return new JdbcTemplate(dynamicDataSource);
}
}
這樣就把我們切換數(shù)據(jù)庫(kù)鎖需要的bean全部交給spring容器中了,使用時(shí)直接通過DataSourceContextHolder.setDataBaseType(DataSourceEnum dataSourceEnum);這個(gè)方法指定數(shù)據(jù)源對(duì)應(yīng)的枚舉類即可。
原理分析:

其實(shí)我們新建數(shù)據(jù)庫(kù)連接的時(shí)候也是通過DataSource來獲取連接的,這里的AbstractRoutingDataSource也是通過了DataSource中的getConnection方法來獲取連接的。

這個(gè)類里維護(hù)了兩個(gè)Map來存儲(chǔ)數(shù)據(jù)庫(kù)連接信息:
@Nullable private Map<Object, Object> targetDataSources; @Nullable private Object defaultTargetDataSource; private boolean lenientFallback = true; private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); @Nullable private Map<Object, DataSource> resolvedDataSources; @Nullable private DataSource resolvedDefaultDataSource;
下面對(duì)上面的幾個(gè)屬性進(jìn)行說明:
其中第一個(gè)targetDataSources是一個(gè)Map對(duì)象,在我們上面第五步創(chuàng)建DynamicDataSource實(shí)例的時(shí)候?qū)⒍鄠€(gè)數(shù)據(jù)源的DataSource類,放入到這個(gè)Map中去,這里的Key是枚舉類,values就是DataSource類。

第二個(gè)defaultTargetDataSource是默認(rèn)的數(shù)據(jù)源,就是DynamicDataSource中唯一重寫的方法來給這個(gè)對(duì)象賦值的。

第三個(gè)lenientFallback是一個(gè)標(biāo)識(shí),是當(dāng)指定數(shù)據(jù)源不存在的時(shí)候是否采用默認(rèn)數(shù)據(jù)源,默認(rèn)是true,設(shè)置為false之后如果找不到指定數(shù)據(jù)源將會(huì)返回null.

第四個(gè)dataSourceLookup是用來解析指定的數(shù)據(jù)源對(duì)象為DataSource實(shí)例的。默認(rèn)是JndiDataSourceLookup實(shí)例,繼承自DataSourceLookup接口。
第五個(gè)resolvedDataSources也是一個(gè)Map對(duì)象,這里是存放指定數(shù)據(jù)源解析后的DataSource對(duì)象。

第六個(gè)resolvedDefaultDataSource是默認(rèn)的解析后的DataSource數(shù)據(jù)源對(duì)象上面的getConnection方法就是從這個(gè)變量中拿到DataSource實(shí)例并獲取連接的。

總結(jié)
- springboot集成@DS注解實(shí)現(xiàn)數(shù)據(jù)源切換的方法示例
- springboot+dynamicDataSource動(dòng)態(tài)添加切換數(shù)據(jù)源方式
- SpringBoot?+DynamicDataSource切換多數(shù)據(jù)源的全過程
- Springboot實(shí)現(xiàn)根據(jù)用戶ID切換動(dòng)態(tài)數(shù)據(jù)源
- 詳細(xì)聊聊SpringBoot中動(dòng)態(tài)切換數(shù)據(jù)源的方法
- Spring配置多數(shù)據(jù)源切換
- SpringBoot AOP方式實(shí)現(xiàn)多數(shù)據(jù)源切換的方法
- Spring配置多個(gè)數(shù)據(jù)源并實(shí)現(xiàn)數(shù)據(jù)源的動(dòng)態(tài)切換功能
相關(guān)文章
Java 獲取當(dāng)前時(shí)間及實(shí)現(xiàn)時(shí)間倒計(jì)時(shí)功能【推薦】
這篇文章主要介紹了Java 獲取當(dāng)前時(shí)間及實(shí)現(xiàn)時(shí)間倒計(jì)時(shí)功能 ,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-05-05
Java結(jié)構(gòu)型設(shè)計(jì)模式之適配器模式詳解
適配器模式,即將某個(gè)類的接口轉(zhuǎn)換成客戶端期望的另一個(gè)接口的表示,主要目的是實(shí)現(xiàn)兼容性,讓原本因?yàn)榻涌诓黄ヅ?,沒辦法一起工作的兩個(gè)類,可以協(xié)同工作。本文將通過示例詳細(xì)介紹適配器模式,需要的可以參考一下2022-09-09
SpringMvc配置靜態(tài)資源訪問路徑的實(shí)現(xiàn)
本文主要介紹了SpringMvc配置靜態(tài)資源訪問路徑的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
詳解基于java的Socket聊天程序——服務(wù)端(附demo)
這篇文章主要介紹了詳解基于java的Socket聊天程序——服務(wù)端(附demo),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2016-12-12
Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(55)
下面小編就為大家?guī)硪黄狫ava基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧,希望可以幫到你2021-08-08
Spring中利用IOC實(shí)現(xiàn)注入的方式
Spring IOC(控制反轉(zhuǎn))實(shí)現(xiàn)依賴注入,將對(duì)象創(chuàng)建和依賴關(guān)系的管理交由Spring容器處理,通過注解或XML配置,實(shí)現(xiàn)對(duì)象之間的松耦合,提高代碼復(fù)用性和可維護(hù)性2023-04-04

