最近在查看動態多數據源,看到了dynamic-datasource-spring-boot-starter庫,地址在:https://github.com/baomidou/dynamic-datasource-spring-boot-starter
這里進行簡單分析,學習其基本原理。
一、注解的引入
這個庫,要求通過DS注解,在自己的業務代碼的方法上,聲明使用哪種數據源。
//如果引入了jar包,通過 spring的META-INF/spring.factories自動引入配置 DynamicDataSourceAutoConfiguration //DynamicDataSourceAutoConfiguration 類,注冊了Bean DynamicDataSourceAnnotationAdvisor,這里實現了對DS注解的攔截器 DynamicDataSourceAnnotationInterceptor
二、注解引起的攔截器
/**
* 這里通過一個線程上下文變量實現了 數據源坐標的傳遞。
* 並且帶有推斷的功能,如果是#開頭的數據源坐標,可以在運行時,通過DsProcessor 進行推斷,推斷結果,放入dsKey中。
* 如果是普通數據源,則直接放在線程上下文里。 這里使用了Deque 來存儲,跟隨着調用線程棧的變化而變化(如果遇到了DS注解,也會入棧出棧)。
*
*/
public class DynamicDataSourceAnnotationInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { try { String dsKey = determineDatasourceKey(invocation); DynamicDataSourceContextHolder.push(dsKey); return invocation.proceed(); } finally { DynamicDataSourceContextHolder.poll(); } } }
/**
* 這里為止,只是在線程里,放了數據源的坐標
*/
三、mybatis插件的配合
/**
* 這里使用了mybatis的插件規范,增加@Intercepts注解,聲明攔截mybatis的什么類、什么方法、什么入參。
* 因為 增、刪、改 都是走的update,這里攔截了mybatis的Executor的query和update方法
*
* 這里主要處理讀寫分離之類的需求,如果當前沒有指定數據源(線程上下文沒有設置),就嘗試主備配置。
* 對於多數據源切換之類的需求,這里可以跳過,沒有實質邏輯
*/
@Intercepts({ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})}) @Slf4j public class MasterSlaveAutoRoutingPlugin implements Interceptor { @Autowired private DynamicDataSourceProperties properties; @Override public Object intercept(Invocation invocation) throws Throwable { Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; boolean empty = true; try { empty = StringUtils.isEmpty(DynamicDataSourceContextHolder.peek()); if (empty) { DynamicDataSourceContextHolder.push(getDataSource(ms)); } return invocation.proceed(); } finally { if (empty) { DynamicDataSourceContextHolder.clear(); } } } }
四、動態數據源
/** * 這里聲明了一個動態數據源,實現了DataSource,注冊給外部使用。如果使用了mybatis的話,mybatis在獲取connection的時候,會在這里獲取connection * * */ @Slf4j public class DynamicRoutingDataSource extends AbstractRoutingDataSource implements InitializingBean, DisposableBean { //通過線程變量,配合出來,獲取最新的一次設置的數據源坐標(函數調用存在嵌套,此時數據源坐標的Deque可能入棧了好幾個坐標了) @Override public DataSource determineDataSource() { return getDataSource(DynamicDataSourceContextHolder.peek()); } //通過DataSource獲取connection @Override public Connection getConnection() throws SQLException { return determineDataSource().getConnection(); } //做選擇策略 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(); }
五、總結
支持多個數據源,支持動態添加、刪除數據源,而且有一些兜底策略;可以根據條件,用代碼動態選擇數據源,支持主備讀寫分離。 感謝原作者的貢獻和開源。
和我具體業務的需求的有一點不同:需要動態控制數據源的創建、檢測和刪除,暫時空閑的數據源連接池要刪除;提供一個批量任務執行框架,可以批量並發執行SQL操作,而且控制其並發度,不超過本地連接池的連接數。