PageHelper引發(fā)的幽靈數(shù)據(jù)問題解析
前言
最近測(cè)試反饋一個(gè)問題,某個(gè)查詢?nèi)啃畔⒌慕涌?,有時(shí)候返回全量數(shù)據(jù),符合預(yù)期,但是偶爾又只返回1條數(shù)據(jù),簡(jiǎn)直就是“見鬼”了,究竟是為什么出現(xiàn)這樣的“幽靈數(shù)據(jù)”呢?
大膽猜測(cè)
首先我們看了下這對(duì)代碼的業(yè)務(wù)邏輯,非常的簡(jiǎn)單,總共沒有幾行代碼,也沒有分頁邏輯,代碼如下:
public List<SdSubscription> findAll() { return sdSubscriptionMapper.selectAll(); }
那么究竟是咋回事呢?講道理不可能出現(xiàn)這種情況的啊,不要慌,我們加點(diǎn)日志,將日志級(jí)別調(diào)整為DEBUG
,讓日志飛一段時(shí)間。
public List<SdSubscription> findAll() { log.info("find the sub start ....."); List<SdSubscription> subs = sdSubscriptionMapper.selectAll(); log.info("find the sub end ....."); return subs; }
果不其然,日志中出現(xiàn)了奇奇怪怪的分頁參數(shù),如下圖所示:
果然是PageHelper
這個(gè)開源框架搞的鬼,我想大家都用過吧,分頁非常方便,那么究竟為什么別人都沒問題,單單就我會(huì)出現(xiàn)問題呢?
PageHelper工作原理
為了回答上面的疑問,我們先看看PageHelper
框架的工作原理吧。
PageHelper
是一個(gè)開源的 MyBatis
分頁插件,它可以幫助開發(fā)者在查詢數(shù)據(jù)時(shí),快速的實(shí)現(xiàn)分頁功能。
PageHelper
的工作原理可以簡(jiǎn)單概括為以下幾個(gè)步驟:
- 在需要進(jìn)行分頁的查詢方法前,調(diào)用
PageHelper
的靜態(tài)方法startPage()
,設(shè)置當(dāng)前頁碼和每頁顯示的記錄數(shù)。它會(huì)將分頁信息放到線程的ThreadLocal
中,那么在線程的任何地方都可以訪問了。 - 當(dāng)查詢方法執(zhí)行時(shí),
PageHelper
會(huì)自動(dòng)攔截查詢語句,如果發(fā)現(xiàn)線程的ThreadLocal
中有分頁信息,那么就會(huì)在其前后添加分頁語句,例如MySQL
中的LIMIT
語句。 - 查詢結(jié)果將被包裝在
Page
對(duì)象中返回,該對(duì)象包含分頁信息和查詢結(jié)果列表。 - 在查詢方法執(zhí)行完畢后,會(huì)在
finally
中清除線程ThreadLocal
中的分頁信息,避免分頁設(shè)置對(duì)其他查詢方法的影響。
PageHelper
的實(shí)現(xiàn)原理主要依賴于攔截器技術(shù)和反射機(jī)制,通過攔截查詢語句并動(dòng)態(tài)生成分頁語句,實(shí)現(xiàn)了簡(jiǎn)單、高效、通用的分頁功能。具體源碼在下圖的類中,非常容易看懂。
明白了PageHelper
的工作原理后,反復(fù)檢查代碼,都沒有調(diào)用過startPage
,debug
查看ThreadLocal
中也沒有分頁信息啊,懵逼中。那我看看別人寫的添加分頁參數(shù)的代碼吧,不看不知道,一看嚇一跳。
原來有位“可愛”的同事竟然在查詢后,加了一個(gè)分頁,就是把分頁信息放到線程的ThreadLocal
中。
那大家是不是有疑問,丁是丁,矛是矛,你的線程關(guān)我何事?這就要說到我們的tomcat了。
Tomcat請(qǐng)求流程
其實(shí)這就涉及到我們的tomcat
相關(guān)知識(shí)了,我們一個(gè)瀏覽器發(fā)一個(gè)接口請(qǐng)求,經(jīng)過我們的tomcat
的,究竟是一個(gè)什么樣的流程呢?
- 客戶端發(fā)送
HTTP
請(qǐng)求到Tomcat
服務(wù)器。 Tomcat
的HTTP
連接器(Connector
)接收到請(qǐng)求,將連接請(qǐng)求交給線程池Executor
處理,解析它,然后將請(qǐng)求轉(zhuǎn)發(fā)給對(duì)應(yīng)的Web應(yīng)用程序。Tomcat
的Web應(yīng)用程序容器(Container
)接收到請(qǐng)求,根據(jù)請(qǐng)求的URL找到對(duì)應(yīng)的Servlet
。
關(guān)于tomcat中使用線程池提交瀏覽器的連接請(qǐng)求的源碼如下:
從而得知,你的連接請(qǐng)求是從線程池從拿的,而拿到的這個(gè)線程恰好是一個(gè)“臟線程”,在ThreadLocal
中放了分頁信息,導(dǎo)致你這邊出現(xiàn)問題。
總結(jié)
后來追問了同事具體原因,才發(fā)現(xiàn)是粗心導(dǎo)致的。有些bug總是出現(xiàn)的莫名其妙,就像生活一樣。所以關(guān)鍵的是我們?cè)谑褂靡恍╅_源框架的時(shí)候一定要掌握底層實(shí)現(xiàn)的原理、核心的機(jī)制,這樣才能夠在解決一些問題的時(shí)候有據(jù)可循。
以上就是PageHelper引發(fā)的幽靈數(shù)據(jù)問題解析的詳細(xì)內(nèi)容,更多關(guān)于PageHelper幽靈數(shù)據(jù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java Validation Api如何實(shí)現(xiàn)自定義注解
這篇文章主要介紹了Java Validation Api如何實(shí)現(xiàn)自定義注解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09spring boot項(xiàng)目快速構(gòu)建的全步驟
這篇文章主要給大家介紹了關(guān)于spring boot項(xiàng)目快速構(gòu)建的全步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09Java8新特性之接口中的默認(rèn)方法和靜態(tài)方法
這篇文章主要介紹了Java8新特性之接口中的默認(rèn)方法和靜態(tài)方法的相關(guān)資料,文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-07-07