AbstractRoutingDataSource是Spring框架中的一個抽象類,可以實現多數據源的動態切換和路由,以滿足複雜的業務需求和提高系統的性能、可擴展性、靈活性。 ...
簡介
AbstractRoutingDataSource
是Spring框架中的一個抽象類,可以實現多數據源的動態切換和路由,以滿足複雜的業務需求和提高系統的性能、可擴展性、靈活性。
應用場景
- 多租戶支持:對於多租戶的應用,根據當前租戶來選擇其對應的數據源,實現租戶級別的隔離和數據存儲。
- 分庫分表:為了提高性能和擴展性,將數據分散到多個資料庫或表中,根據分片規則來選擇正確的數據源,實現分庫分表。
- 讀寫分離:為了提高資料庫的讀寫性能,可能會採用讀寫分離的方式,根據讀寫操作的類型來選擇合適的數據源,實現讀寫分離。
- 數據源負載均衡:根據負載均衡策略來選擇合適的數據源,將請求均勻地分配到不同的數據源上,提高系統的整體性能和可伸縮性。
- 多資料庫支持:在一些場景下,可能需要同時連接多個不同類型的資料庫,如關係型資料庫、NoSQL資料庫等。根據業務需求選擇不同類型的數據源,實現對多資料庫的支持。
實現原理
1.AbstractRoutingDataSource
實現了DataSource
介面,作為一個數據源的封裝類,負責路由資料庫請求到不同的目標數據源
2.該類中定義了一個determineTargetDataSource
方法,會獲取當前的目標數據源標識符,進而返回真正的數據源;
值得註意的是:其中determineCurrentLookupKey
為抽象方法,明顯是要讓用戶自定義實現獲取數據源標識的業務邏輯。
3.當系統執行資料庫操作之前,會先獲取數據源鏈接,即調用getConnection
方法,該類重寫的getConnection
方法,會獲取到真正的目標數據源,進而將資料庫操作委托給目標數據源進行處理。
讀寫分離實現V1版
- yml中配置主從資料庫連接信息
spring:
datasource:
business-master:
url: jdbc:mysql://ip1:3306/xxx
username: c_username
password: p1
business-slaver:
url: jdbc:mysql://ip2:3306/xxx
username: c_username
password: p2
2.讀取yml中的主從數據源配置
@Data
@ConfigurationProperties(prefix = "spring.datasource")
@Component
public class DataSourcePropertiesConfig {
/**
* 主庫配置
*/
DruidDataSource businessMaster;
/**
* 從庫配置
*/
DruidDataSource businessSlaver;
}
3.自定義動態數據源類DynamicRoutingDataSource
,繼承AbstractRoutingDataSource
類,並重寫determineCurrentLookupKey
方法,定義獲取目標數據源標識的邏輯。
此處的邏輯為:定義一個DataSourceHolder
類,將數據源標識放到ThreadLocal
中,當需要時從ThreadLocal
中獲取。
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
/**
* 獲取目標數據源標識
*/
@Override
protected Object determineCurrentLookupKey() {
return DataSourceHolder.getDbName();
}
}
public class DataSourceHolder {
/**
* 當前線程使用的 數據源名稱
*/
private static final ThreadLocal<String> THREAD_LOCAL_DB_NAME = new ThreadLocal<>();
/**
* 設置數據源名稱
*/
public static void setDbName(String dbName) {
THREAD_LOCAL_DB_NAME.set(dbName);
}
/**
* 獲取數據源名稱,為空的話預設切主庫
*/
public static String getDbName() {
String dbName = THREAD_LOCAL_DB_NAME.get();
if (StringUtils.isBlank(dbName)) {
dbName = DbNameConstant.MASTER;
}
return dbName;
}
/**
* 清除當前數據源名稱
*/
public static void clearDb() {
THREAD_LOCAL_DB_NAME.remove();
}
}
4.創建動態數據源DynamicRoutingDataSource
對象,並註入到容器中。這裡創建了主從兩個數據源,併進行了初始化,分別為其設置了數據源標識並放到了DynamicRoutingDataSource
對象中,以便後面使用。
若為多個數據源,可參考此處進行批量定義。
@Configuration
public class DataSourceConfig {
@Autowired
private DataSourcePropertiesConfig dataSourcePropertiesConfig;
/**
* 主庫數據源
*/
public DataSource masterDataSource() throws SQLException {
DruidDataSource businessDataSource = dataSourcePropertiesConfig.getBusinessMaster();
businessDataSource.init();
return businessDataSource;
}
/**
* 從庫數據源
*/
public DataSource slaverDataSource() throws SQLException {
DruidDataSource businessDataSource = dataSourcePropertiesConfig.getBusinessSlaver();
businessDataSource.init();
return businessDataSource;
}
/**
* 動態數據源
*/
@Bean
public DynamicRoutingDataSource dynamicRoutingDataSource() throws SQLException {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
Map<Object, Object> targetDataSources = new HashMap<>(2);
targetDataSources.put("master", masterDataSource());
targetDataSources.put("slaver", slaverDataSource());
dynamicRoutingDataSource.setDefaultTargetDataSource(masterDS);
dynamicRoutingDataSource.setTargetDataSources(targetDataSources);
dynamicRoutingDataSource.afterPropertiesSet();
return dynamicRoutingDataSource;
}
}
5.自定義一個註解,指定資料庫。
可以將一些常用的查詢介面自動路由到讀庫,以減輕主庫壓力。
@Documented
@Inherited
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSourceSwitch {
/**
* 數據源名稱,預設主庫
*/
String dbName() default "master";
}
6.定義一個切麵,攔截所有Controller
介面,使用DataSourceSwitchRead
註解的方法,將統一路由到讀庫查詢
@Aspect
@Component
@Slf4j
public class DataSourceAspect {
/**
* 切庫,若為多個從庫,可在這裡添加負載均衡策略
*/
@Before(value = "execution ( * com.jd.gyh.controller.*.*(..))")
public void changeDb(JoinPoint joinPoint) {
Method m = ((MethodSignature) joinPoint.getSignature()).getMethod();
DataSourceSwitch dataSourceSwitch = m.getAnnotation(DataSourceSwitch.class);
if (dataSourceSwitch == null) {
DataSourceHolder.setDbName(DbNameConstant.MASTER);
log.info("switch db dbName = master");
} else {
String dbName = dataSourceSwitch.dbName();
log.info("switch db dbName = {}", dbName);
DataSourceHolder.setDbName(dbName);
}
}
}
作者:京東科技 郭艷紅
來源:京東雲開發者社區