Spring 下,關於動態資料來源的事務問題的探討

青石路發表於2020-04-20

開心一刻

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

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

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

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

  蟒蛇:你不也親了它嗎?

前情回顧

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

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

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

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

相關文章