sqlhelper整合dynamic多資料來源的分頁問題(非教學向)

GuessBUG發表於2020-10-24

一.問題描述

最近接手(頂鍋)了公司的框架維護工作,第一項任務就是整合dynamic多資料來源框架。(dynamic官方使用文件,本文不是教學,有興趣的小夥伴可以自己查閱文件)。整合dynamic之後,一切都很順利,但是測試到SQLHelper框架的分頁功能,出錯了:SQLHelper分頁功能,全部是按照dynamic指定的primary資料來源來處理分頁sql的。比如我配置了mysql和oracle兩個資料來源,並且指定mysql為primary主資料來源,然後不管使用哪個資料來源進行查詢,SQLHelper都是按照primary指定的主資料來源mysql進行分頁處理,導致用oracle資料來源時分頁語句sql報錯。

二.解決思路

1.找到分頁sql的處理入口

通過斷點除錯,找到了SQLHelper的com.jn.sqlhelper.dialect.internal.Dialect類和處理語句有關(實際上Dialect是一個介面,抽象子類AbstractDialect的屬性LimitHandler才是實際sql的邏輯處理類,processSql是處理方法入口。為了方便理解,就以Dialect概括,一個Dialect對應一種資料來源的處理)
在這裡插入圖片描述

2.Dialect和databaseId有關

databaseId其實就是資料來源型別,Dialect是處理sql的類,每一種資料來源對應一個Dialect,通過databaseId獲取對應的Dialect然後進行sql加工處理,com.jn.sqlhelper.mybatis.plugins.pagination的PaginationHandler類beginIfSupportsLimit方法:
在這裡插入圖片描述

3.實際出問題的地方

其實就是databaseId的獲取,com.jn.sqlhelper.mybatis包下MybatisUtils類getDatabaseId方法:

在這裡插入圖片描述

紅框才是罪魁禍首(暫且先不看藍框),通過斷點除錯,發現獲取的是org.apache.ibatis.session.Configuration類的databaseId。那問題來了,Configuration類的databaseId是什麼時候設定的呢?通過Configuration的setDatabaseId方法的斷點除錯,發現該方法是專案啟動的時候才呼叫。org.mybatis.spring.SqlSessionFactoryBean類的buildSqlSessionFactory方法
在這裡插入圖片描述

這也解釋通了為什麼SQLHelper只按照某種資料來源來處理分頁語句

4.為什麼按照primary指定的主資料來源處理?

一切從簡,上關鍵程式碼(接Configuration的setDatabaseId方法):
com.baomidou.dynamic.datasource.DynamicRoutingDataSource類的determineDataSource方法
在這裡插入圖片描述
com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder類的peek()方法返回當前執行緒/程式碼使用的資料來源名稱,啟動時沒有觸發push操作,所以返回為空。
在這裡插入圖片描述
空值則返回primary指定的預設資料庫,所以Configuration的databaseId是dynamic在配置檔案用primary指定的主資料來源:
在這裡插入圖片描述
5.DynamicDataSourceContextHolder的push:
dynamic會將當前執行緒使用的資料來源存入threadlocal裡
com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationInterceptor.invoke方法,會將當前執行緒使用的資料來源名稱存入
在這裡插入圖片描述
這個push觸發的機制,應該是dynamic註解掃描到的地方。這也解釋了為什麼啟動的時候peek返回空,因為沒有觸發push操作。

三.解決方案:

既然知道問題所在,那麼就有解決思路了:MybatisUtils的getDatabaseId方法,返回正確的資料就行。

1.不走全域性獲取databaseId

如果能提前知道現在的資料來源是什麼,然後通過這個資料來源獲取databaseId就好了。巧的是DynamicDataSourceContextHolder類的peek()是一個靜態方法,並且入參MappedStatement ms可以獲取到dynamic配置的所有資料來源資訊:
在這裡插入圖片描述

2.通過connection獲取databaseId

其實就是二.3的藍框放前面就行,由於整合了連線池,所以不需要擔心查詢之前多連線一次資料庫。tx.getConnection()原始碼有興趣的也可以翻一下哦。

3.後續版本優化

四.小結

  1. SQLHelper根據databaseId(資料來源型別)處理分頁語句的類是LimitHandler;databaseId和LimitHandler、Dialect,是1:1:1的關係。
  2. springboot+tx.mybatis+dynamic+SQLHelper的技術選型下,SQLHelper在dynamic情況下分頁處理有問題。
  3. 處理方案經實踐,mybatis是完全可以適配的。不知道其他orm框架是否適用!

五.彩蛋時間

最近終於稍微脫離了CRDU的業務程式碼,開始往底層摸索了。框架需要適配dynamic,其實我也是什麼都不懂,沒辦法只能查資料看看dynamic是什麼,然後通過斷點除錯的方式探索開源框架的處理流程,說實話,最後發現問題並且解決問題的時候,還是有一點自豪感和成就感的!
框架這個版本的需求功能,需要用到的技術棧其實我基本沒聽過,但是有壓力就有動力,能讓自己多學一些總是好的!
另外附上和SQLHelper作者在github上的討論地址:此次論劍,略輸一籌(滑稽,HSPCode就是本萌新)

萌新發言,不喜勿噴,歡迎大佬指出不當之處!

相關文章