開心一刻
毒蛇和蟒蛇在討論誰的捕獵方式最高效。
毒蛇:我只需要咬對方一口,一段時間內它就會逐漸喪失行動能力,最後死亡。
蟒蛇冷笑:那還得等生效時間,我只需要纏住對方,就能立刻致它於死地。
毒蛇大怒:你纏它身子,你下賤!
蟒蛇:你不也親了它嗎?
前情回顧
看著文章的標題,不知道大家能否想到具體是什麼問題,如果你有點懵,那就對了! (你不懵的話我這篇文章就沒存在的意義了,嘿嘿)
在給大家指出具體是什麼問題時,我們先來回顧一些內容
Spring 事務原理
相信大家對這個都能說上來一些,Spring 事務是 Spring AOP 的一種具體應用,底層依賴的是動態代理
大致流程類似如下
通過代理物件來呼叫目標物件,而在代理物件中有事務相關的增強處理
具體細節可參考以下文章
結合 ThreadLocal 來看 Spring 事務原始碼,感受下清泉般的洗滌!
Spring 動態資料來源原理
原理解密 → Spring AOP 實現動態資料來源(讀寫分離),底層原理是什麼中已經詳細介紹過了,流程大體如下
Spring AOP → 將我們指定的 lookupKey 放入 ThreadLocal
ThreadLocal → 執行緒內共享 lookupKey
DynamicDataSource → 對多資料來源進行封裝,根據 ThreadLocal 中的 lookupKey 動態選擇具體的資料來源
有什麼問題
既然事務和動態資料來源都是 Spring AOP 的具體應用,那麼代理就存在先後順序了
要麼是
要麼是
我們來看看這兩者有什麼區別
事務在前,動態資料來源在後
此時,事務的前置增強處理會先生效,那麼此時開始事務獲取的 Connection 從哪來 ? 肯定是從 DynamicDataSource 來,因為我們給事務管理器配置的就是它
@Bean public PlatformTransactionManager transactionManager(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) { // 配置事務管理, 使用事務時在方法頭部新增@Transactional註解即可 return new DataSourceTransactionManager(dynamicDataSource); }
既然是從 DynamicDataSource 獲取的 Connection,那 DynamicDataSource 根據 lookupKey 獲取 Connection 的時候,會從 masterDataSource 資料來源獲取還是從 slaveDataSource 資料來源獲取 ?因為此時還未將 lookupKey 繫結到當前執行緒,那麼 DynamicDataSource 會從預設資料來源獲取,而我們配置的預設資料來源是 slaveDataSource
/** * 獲取當前執行緒的資料來源 * @return */ public static DataSourceType getDataSourceType() { return HOLDER.get() == null ? DataSourceType.SLAVE : HOLDER.get(); }
說白了,此時的動態資料來源對事務不生效,事務始終從預設資料來源獲取 Connection,而沒有動態的效果,這就是問題了
Talk is cheap. Show me the code,我們來看看是不是真的如上所說
192.168.0.112 正是我們的從庫,對應的就是我們的預設資料來源 slaveDataSource
動態資料來源在前,事務在後
此時,動態資料來源的前置增強會先執行,DynamicDataSource 需要的 lookupKey 會先於事務繫結到當前執行緒,那麼事務從 DynamicDataSource 獲取 Connection 的時候就能根據當前執行緒的 lookupKey 來動態選擇 masterDataSource 還是 slaveDataSource
此種情況是沒有問題的
解決問題
總結下問題:如何保證事務中的動態資料來源也有動態的效果,也就是如何保證動態資料來源的前置增強先於事務
我們知道 Spring AOP 是能夠指定順序的,只要我們顯示的指定動態資料來源的 AOP 先於 事務的 AOP 即可;如何指定順序,常用的方式是實現 Order 介面,或者使用 @Order 註解,Order 的值越小,越先執行,所以我們只需要保證動態資料來源的 Order 值小於事務的 Order 值即可
我們先來看看事務的 Order 值預設是多少,在 EnableTransactionManagement 註解中
/** * Indicate the ordering of the execution of the transaction advisor * when multiple advices are applied at a specific joinpoint. * <p>The default is {@link Ordered#LOWEST_PRECEDENCE}. */ int order() default Ordered.LOWEST_PRECEDENCE;
預設是最低階別(值非常大),那麼我們只需要保證動態資料來源的 Order 比這個值小就好,我們就取 1
@Component @Slf4j @Order(1) public class DynamicDataSourceAspect {
我們在來看看是否真的可行
已經不是預設的 slaveDataSource ,而是我們指定的 masterDataSource(通過 @MasterSlave(MASTER) 指定)
至此,相信大家已經弄清楚了有什麼問題,以及如何解決它
什麼,還沒理解 ? 你過來,我保證不打死你
總結
1、不只是動態資料來源和事務,只要涉及到多個 AOP,就可能會有順序問題,這是值得大家注意的
2、相關約束
主資料庫執行 INSERT UPDATE DELETE 操作,可能還有部分 SELECT 操作(主從同步多少有延時)
從資料庫只執行 SELECT 操作
預設資料來源最好設定成主資料來源,防止粗心將更新操作執行到了從資料庫;樓主之所以設定成從資料來源,是考慮到絕大多數資料庫操作是查詢,這樣可以減少程式碼量;具體怎麼選,需要大家結合實際情況來決定