我最初的想法是: 讀方法走讀庫,寫方法走寫庫(一般是主庫),保證在Spring提交事務之前確定數據源.

保證在Spring提交事務之前確定數據源,這個簡單,利用AOP寫個切換數據源的切面,讓他的優先級高於Spring事務切面的優先級。至於讀,寫方法的區分可以用2個注解。
但是如何切換數據庫呢? 我完全不知道!多年經驗告訴我
我搜索了一些網文,發現都提到了一個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讀寫分離的關鍵了。
仔細閱讀了三個方法,基本上跟方法名的意思一致。setTargetDataSources設置備選的數據源集合。 setDefaultTargetDataSource設置默認數據源,determineCurrentLookupKey決定當前數據源的對應的key。
但是我很好奇這3個方法都沒有包含切換數據庫的邏輯啊!我仔細閱讀源碼發現一個方法,determineTargetDataSource方法,其實它才是獲取數據源的實現。源碼如下:

簡單說就是,根據determineCurrentLookupKey獲取的key,在resolvedDataSources這個Map中查找對應的datasource!,注意determineTargetDataSource方法竟然不使用的targetDataSources!
那一定存在resolvedDataSources與targetDataSources的對應關系。我接着翻閱代碼,發現一個afterPropertiesSet方法(Spring源碼中InitializingBean接口中的方法),這個方法將targetDataSources的值賦予了resolvedDataSources。源碼如下:

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前邊執行。
進一步了解理論后,讀寫分離的方式則基本上出現在眼前了。(“下列方法不唯一”)
先寫一個類繼承AbstractRoutingDataSource,實現determineCurrentLookupKey方法,和afterPropertiesSet方法。afterPropertiesSet方法中調用setDefaultTargetDataSource和setTargetDataSources方法之后調用super.afterPropertiesSet。
之后定義一個切面在事務切面之前執行,確定真實數據源對應的key。但是這又出現了一個問題,如何線程安全的情況下傳遞每個線程獨立的key呢?沒錯使用ThreadLocal傳遞真實數據源對應的key。
ThreadLocal,Thread的局部變量,確保每一個線程都維護變量的一個副本
到這里基本邏輯就想通了,之后就是寫了。
DataSourceContextHolder 使用ThreadLocal存儲真實數據源對應的key

DataSourceAopAspect 切面切換真實數據源對應的key,並設置優先級保證高於事務切面

RoutingDataSouceImpl實現AbstractRoutingDataSource的邏輯

基本邏輯實現完畢了就進行,通用設置,設置數據源,事務,SqlSessionFactory等

其他代碼,就不在這里贅述了,有興趣可以移步完整代碼。
使用Spring寫讀寫分離,其核心就是AbstractRoutingDataSource,源碼不難,讀懂之后,寫個讀寫分離就簡單了!。
AbstractRoutingDataSource重點回顧:
AbstractRoutingDataSource,內部有一個Map<Object,DataSource>的域resolvedDataSources
determineTargetDataSource方法通過determineCurrentLookupKey方法獲得key,進而從map中取得對應的DataSource。
setTargetDataSources 設置 targetDataSources
setDefaultTargetDataSource 設置 defaultTargetDataSource,
targetDataSources和defaultTargetDataSource 在afterPropertiesSet分別轉換為resolvedDataSources和resolvedDefaultDataSource。
targetDataSources,defaultTargetDataSource的賦值一定要在afterPropertiesSet前邊執行。
這周確實有點忙,周五花費了些時間不過總算實現了自己的諾言。
完成承諾不容易,喜歡您就點個贊!
