排查並解決由 PageHelper 導致的多執行緒重用問題

Mondo 科技 更新 2024-02-01

1.PageHelper 方法使用靜態 ThreadLocal 引數,該引數在 startpage() 呼叫後自動清除 ThreadLocal 物件,緊跟 Mybatis 查詢方法。

2.當執行緒首次執行方法 a 的 pagehelper 時startpage(int pagenum, int pagesize),在執行 SQL 語句之前,它過早結束,因為 ** 丟擲異常。

3.此執行緒由另乙個請求重用,並基於當前的 pagenum 和 pagesize 引數,執行方法 b 中的 SQL 語句。

4.方法B的SQL掃瞄整個表,查詢出所有滿足條件的資料,因此由於方法A的分頁引數限制了實際方法B中滿足條件的資料量,因此方法B的查詢結果不正確。

如果你看一下方法a的**,你會發現你正在使用pagehelper在startpage之後,Mybatis查詢SQL之前有很多判斷邏輯,問題發生在紅色異常判斷的中間。

當方法 B 執行第乙個 SQL 查詢語句時,方法 B 的查詢也會因為重用執行緒中方法 A 中的 threadlocal(pagenum, pagesize)引數而限制分頁引數。

a.方法 A 提前丟擲異常且不執行 mybatis 查詢方法的日誌截圖。

b.對 mybatis 查詢方法執行的方法 b 的截圖。

pagehelper 方法使用靜態 threadlocal 引數,並且 pagination 引數繫結到執行緒。

只要你能保證 PageHelper 方法後面跟著 MyBatis 查詢方法,它就是安全的。 因為 PageHelper 自動清除了 ThreadLocal 儲存的物件 Finally.

從上圖中我們可以發現,當乙個請求來臨時,它會獲取儲存當前請求的執行緒的 threadlocal,並呼叫本地頁面get() 來檢查當前執行緒是否有未執行的分頁配置,然後使用 setLocalPage(page) 方法設定執行緒的分頁配置。

@override public object intercept(invocation invocation) throws throwable else checkdialectexists();list resultlist;呼叫該方法以確定是否需要分頁,如果不需要,則返回結果 if (!)。dialect.skip(ms, parameter, rowbounds)) resultlist = executorutil.pagequery(dialect, executor, ms, parameter, rowbounds, resulthandler, boundsql, cachekey); else return dialect.afterpage(resultlist, parameter, rowbounds); finally }
我們需要注意的是mybatis什麼時候使用這個threadlocal,也就是什麼時候獲取到分頁引數?

如前所述,當程式執行 SQL 介面對映器方法時,它將被 ***PageInterceptor 攔截。

PageHelper 其實是 Mybatis 的乙個分頁外掛程式,它的實現原理是通過 PageInterceptor 實現分頁,我們只關注 intercept 方法。

這裡使用 skip 方法設定分頁引數,內部呼叫方法:

page page = pageparams.getpage(parameterobject, rowbounds);
繼續跟蹤 getpage(),發現此方法的第一行獲取 threadlocal 的值:

page page = pagehelper.getlocalpage();
resultlist = executorutil.pagequery(dialect, executor, ms, parameter, rowbounds, resulthandler, boundsql, cachekey);
這就是分頁方法,它根據我們通過 threadlocal 獲取的頁面,決定在執行分頁之前是否進行分頁。

resultlist = executor.query(ms, parameter, rowbounds, resulthandler, cachekey, boundsql);
這是乙個非分頁方法,我們可以想一想,如果執行緒負載在使用後沒有被清除,那麼當執行非分頁方法時,那麼限制就會被串聯到SQL的後面。 為什麼即使你不分割它也不必拼接它? 讓我們回顧一下前面提到的方言skip(ms, parameterobject, rowbounds):

如上圖所示,只要獲取到頁面,SQL 就會使用 executorutilPageQuery 分頁邏輯,最終導致不可預見的情況。

事實上,pagehelper 對分頁後的 threalocal 有明確的處理。

在 intercept 方法結束時,執行 SQL 方法後清除頁面快取

看看這個 afterall() 方法:

只需關注 clearpage():

總的來說,似乎不會有任何問題,但我們可以考慮極端情況的集中:

如果使用startpage(),但是對應的SQL沒有執行,那麼就說明當前執行緒threadlocal有分頁引數,但是沒有使用,下次請求使用這個執行緒的時候就會出現問題。

如果在程式執行 SQL 之前發生異常,則無法執行 FINALLYclearpage()方法,這也會導致執行緒的 ThreadLocal 被汙染。

所以,官方給我們的建議是,在使用 PageHelper 進行分頁時,SQL 執行的 ** 後面應跟 startpage() 方法

除此之外,我們可以在相關方法存在之前手動呼叫 clearpage() 方法。

1.請確保在 mybatis 查詢方法之後立即呼叫 pagehelper 方法,並且在查詢之前不要編寫任何邏輯處理,因為任何 ** 都可能產生異常並導致執行緒重用問題。

2.如果不合理的 ** 原件太多,沒有辦法一一修改,可以考慮在控制器層新增切片,在 jsf 介面中新增過濾器,手動呼叫 clearpage() 方法。 **示例如下:

filter@slf4jpublic類 BSCJSFASPECTFORPAGEHELPER for JSF 介面擴充套件了 AbstractFilter @override public responseMessage invoke(requestMessage requestMessage) catch (exception e) return getnext()。invoke(requestmessage);XML 配置
Slice for controller@aspect@component@slf4jpublic 類 bscaspectforpagehelper @before("bscaspectforpagehelper()") public void dobefore(joinpoint joinpoint) catch (exception e) }
作者:京東物流 王松.

*:京東雲開發者社群 自猿齊說技術 **請註明**。

相關問題答案

    一次艱苦的工作,一次成功的機會

    在人生的道路上,我們經常會遇到各種各樣的挫折和困難。有些人會選擇放棄,而另一些人會選擇堅持努力。其實,每一次努力都是成功的機會,因為只有通過努力,我們才能逐漸接近自己的目標。努力是一種態度,它代表著我們對生活和工作的認真和奉獻。當我們努力工作時,我們會全心全意地投入到我們所做的事情中,無論是習學習 ...

    每一次受傷都是一次成長

    關注墨志,了解更多精彩內容 正文 油墨知識 在我們的人生旅途中,我們會經歷很多事情,有些事情會傷害我們,但每一次受傷,都是一種成長。因為每一次受傷,我們都會變得更堅強 更勇敢 更有責任感。俗話說 成長是屢次失敗和受傷後的反思和積累。說。當我們遇到挫折時,我們應該從中吸取教訓,將失敗轉化為成功的力量。...

    我想和你一起笑的每一刻,因為我知道這不是每一天

    保持你微笑的每一刻。生活中總有一些時刻,就像明亮的星星,照亮了我們的旅程。這些時刻可能是感人的重逢,可能是難忘的離別,也可能是漫不經心的微笑。然而,對我來說,我和你微笑的每一刻都像是我應該珍藏的寶石。你的笑容,像春天溫暖的陽光,灑在我的心裡。那些與你共度的時刻似乎被你的笑容照亮並變得閃耀。我要保留這...

    乙個29歲的女人因為同學聚會而有婚外情,她應該告訴丈夫嗎?

    周女士和丈夫結婚年,雖然生活不是很幸福,但還算過得去。兩人是大學同學,丈夫從大二開始就向周女士求愛,儘管當時生活條件艱苦,但兩人還是走到了一起。婚後,丈夫忙於工作,而周女士則負責照顧孩子和家務,過著平凡的生活。然而,一次同學聚會周女士的生活發生了翻天覆地的變化。周女士在高中時是女神,當時她迷戀上了自...

    “自律挑戰參與者因揉眼睛而被淘汰,社會質疑嚴格的規則”。

    近日,來自長沙的陳先生參加了一家公司的 自律挑戰 專案,在社會上引起了廣泛的熱議。挑戰賽要求參賽者在固定的酒店房間內堅持不玩手機 準時上班連續天,才有機會獲得萬元高額獎金。然而,出人意料的是,陳先生在不到一天的時間裡就因為揉眼睛而被淘汰。根據挑戰規則,挑戰者不能遮住臉,如果一次遮住臉超過秒,則視為失...