摘要:此時,動態數據源的前置增強會先執行,DynamicDataSource 需要的 lookupKey 會先於事務綁定到當前線程,那麼事務從 DynamicDataSource 獲取 Connection 的時候就能根據當前線程的 lookupKey 來動態選擇 masterDataSource 還是 slaveDataSource。1、不只是動態數據源和事務,只要涉及到多個 AOP,就可能會有順序問題,這是值得大家注意的。

開心一刻

毒蛇和蟒蛇在討論誰的捕獵方式最高效。

毒蛇:我只需要咬對方一口,一段時間內它就會逐漸喪失行動能力,最後死亡。

蟒蛇冷笑:那還得等生效時間,我只需要纏住對方,就能立刻致它於死地。

毒蛇大怒:你纏它身子,你下賤!

蟒蛇:你不也親了它嗎?

前情回顧

看着文章的標題,不知道大家能否想到具體是什麼問題,如果你有點懵,那就對了! (你不懵的話我這篇文章就沒存在的意義了,嘿嘿)

在給大家指出具體是什麼問題時,我們先來回顧一些內容

Spring 事務原理

相信大家對這個都能說上來一些,Spring 事務是 Spring AOP 的一種具體應用,底層依賴的是動態代理

大致流程類似如下

通過代理對象來調用目標對象,而在代理對象中有事務相關的增強處理

具體細節可參考以下文章

Spring 事務源碼解析

結合 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 操作

默認數據源最好設置成主數據源,防止粗心將更新操作執行到了從數據庫;樓主之所以設置成從數據源,是考慮到絕大多數數據庫操作是查詢,這樣可以減少代碼量;具體怎麼選,需要大家結合實際情況來決定

相關文章