多租户多数据源实现
最近遇到了做多數據源多需求,我們多系統是基于多租戶多,要求是不同多租戶能訪問不同多數據源,而達到提高性能和良好的容災能力。
我們是基于druid+mysql+springboot的:
那我了解到Spring boot提供了AbstractRoutingDataSource的抽象類根據用戶定義的規則選擇當前的數據源,這樣我們可以在執行查詢之前,設置使用的數據源。實現可動態路由的數據源,在每次數據庫查詢操作前執行。它的抽象方法determineCurrentLookupKey() 決定使用哪個數據源。
AbstractRoutingDataSource
org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 源碼的介紹:
| /** Abstract {@link javax.sql.DataSource} implementation that routes {@link #getConnection()} ?* calls to one of various target DataSources based on a lookup key. The latter is usually ?* (but not necessarily) determined through some thread-bound transaction context. ?* ?* @author Juergen Hoeller ?* @since 2.0.1 ?* @see #setTargetDataSources ?* @see #setDefaultTargetDataSource ?* @see #determineCurrentLookupKey() ?*/ ? |
AbstractRoutingDataSource就是DataSource的抽象,基于lookup key的方式在多個數據庫中進行切換。重點關注setTargetDataSources,setDefaultTargetDataSource,determineCurrentLookupKey三個方法。那么AbstractRoutingDataSource就是Spring多數據源的關鍵了。
但是好像3個方法都沒有包含切換數據庫的邏輯啊!仔細閱讀源碼發現一個方法,determineTargetDataSource方法,其實它才是獲取數據源的實現。源碼如下:
| ? ? //切換數據庫的核心邏輯 ? ? protected DataSource determineTargetDataSource() { ?? ??? ?Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); ?? ??? ?Object lookupKey = determineCurrentLookupKey(); ?? ??? ?DataSource dataSource = this.resolvedDataSources.get(lookupKey); ?? ??? ?if (dataSource == null && (this.lenientFallback || lookupKey == null)) { ?? ??? ??? ?dataSource = this.resolvedDefaultDataSource; ?? ??? ?} ?? ??? ?if (dataSource == null) { ?? ??? ??? ?throw new IllegalStateException ? ? ? ? ? ? ? ("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); ?? ??? ?} ?? ??? ?return dataSource; ?? ?} ? ? //之前的2個核心方法 ?? ?public void setTargetDataSources(Map<Object, Object> targetDataSources) { ?? ??? ?this.targetDataSources = targetDataSources; ?? ?} ? ? public void setDefaultTargetDataSource(Object defaultTargetDataSource) { ?? ??? ?this.defaultTargetDataSource = defaultTargetDataSource; ?? ?} |
簡單說就是,根據determineCurrentLookupKey獲取的key,在resolvedDataSources這個Map中查找對應的datasource!,注意determineTargetDataSource方法竟然不使用的targetDataSources!
那一定存在resolvedDataSources與targetDataSources的對應關系。接著翻閱代碼,發現一個afterPropertiesSet方法(Spring源碼中InitializingBean接口中的方法),這個方法將targetDataSources的值賦予了resolvedDataSources。源碼如下
?
| ?? ?@Override ?? ?public void afterPropertiesSet() { ?? ??? ?if (this.targetDataSources == null) { ?? ??? ??? ?throw new IllegalArgumentException("Property 'targetDataSources' is required"); ?? ??? ?} ?? ??? ?this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size()); ?? ??? ?for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) { ?? ??? ??? ?Object lookupKey = resolveSpecifiedLookupKey(entry.getKey()); ?? ??? ??? ?DataSource dataSource = resolveSpecifiedDataSource(entry.getValue()); ?? ??? ??? ?this.resolvedDataSources.put(lookupKey, dataSource); ?? ??? ?} ?? ??? ?if (this.defaultTargetDataSource != null) { ?? ??? ??? ?this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource); ?? ??? ?} ?? ?} |
afterPropertiesSet?方法,熟悉Spring的都知道,它在bean實例已經創建好,且屬性值和依賴的其他bean實例都已經注入以后執行。
也就是說調用,targetDataSources,defaultTargetDataSource的賦值一定要在afterPropertiesSet前邊執行。
AbstractRoutingDataSource簡單總結:
AbstractRoutingDataSource,內部有一個Map<Object,DataSource>的域resolvedDataSources
determineTargetDataSource方法通過determineCurrentLookupKey方法獲得key,進而從map中取得對應的DataSource。
setTargetDataSources 設置 targetDataSources
setDefaultTargetDataSource 設置 defaultTargetDataSource,
targetDataSources和defaultTargetDataSource 在afterPropertiesSet分別轉換為resolvedDataSources和resolvedDefaultDataSource。
targetDataSources,defaultTargetDataSource的賦值一定要在afterPropertiesSet前邊執行。
簡單寫下為代碼邏輯:
1、先寫一個類繼承AbstractRoutingDataSource,實現determineCurrentLookupKey方法,和afterPropertiesSet方法。afterPropertiesSet方法中調用setDefaultTargetDataSource和setTargetDataSources方法之后調用super.afterPropertiesSet。
2、定義一個切面在事務切面之前執行,確定真實數據源對應的key
3、用ThreadLocal傳遞真實數據源對應的key
4、定義一個druidDataSourceCreator類,每次創建數據源都從這里取
參考文章:1、https://blog.csdn.net/qq_36903131/article/details/89372011
? ? ? ? ? ? ? ? ? 2、如果自己不想實現可以使用mybatis-plus的實現https://github.com/baomidou/dynamic-datasource-spring-boot-starter
? ? ? ? ? ? ? ? ? ? ? ? 更建議是自己參考其設計模式進行設計
? ? ? ? ? ? ? ? ?3、https://blog.csdn.net/floor2011/article/details/100907108
?
?
?
總結
- 上一篇: PostgreSQL mysql 兼容性
- 下一篇: PostgreSQL 中的引号与大小写