Mybatis第三方PageHelper分頁插件的使用與原理
?用法

?
此時commentAnalyses為Page對象(PageHelper插件包內(nèi)定義的)
?
而Page對象繼承自JDK中的ArrayList,擴展并封裝了一些page相關的字段,如頁碼,每頁大小,總記錄數(shù),總頁數(shù)等。

?原理
我們就加了一行,它是如何幫助我們實現(xiàn)分頁的呢?請往下看。
PageHelper.startPage做了什么
我們看這一行PageHelper.startPage(pageIndex, pageSize);做了什么。這個類中重載了好多個startPage方法,最終調用到如下的一個方法

?可以看到該方法將分頁信息作為構造器參數(shù)實例化Page對象,調用SqlUtil.getLocalPage()獲取一個舊的Page對象,最后調用SqlUtil.setLocalPage(page);把新創(chuàng)建的Page對象set進去。
我們看看這兩個方法做了什么。如下,很簡單,從ThreadLocal中獲取Page對象,將Page對象set到ThreadLocal中。知道ThreadLocal作用的不用多說,不知道的可以理解為用于保存本地變量,并與線程綁定。

?看到這我們暫且將這一行的作用記為,創(chuàng)建并保存Page對象(分頁信息)到ThreadLocal中。
Page分頁信息在哪使用
那么既然保存了,就有使用的地方。
我通過代碼追蹤的方式定位到被調用的地方,通過回溯,發(fā)現(xiàn)是從這個類com.github.pagehelper.dialect.AbstractDialect發(fā)起調用的

點進去看了一下,主要是取出Page對象用于做一些判斷,或保存page相關的信息。應該后續(xù)會涉及到

攔截器
上述AbstractDialect類中的這些方法再回溯,指向了
com.github.pagehelper.util.SqlUtil#doIntercept方法,intercep調用了doIntercep方法,

繼續(xù)往上追蹤來到了com.github.pagehelper.PageHelper#intercept,這是Interceptor接口的方法。

然后是org.apache.ibatis.plugin.Plugin#invoke調用了com.github.pagehelper.PageHelper#intercept。
可以看到PageHelper實現(xiàn)了Interceptor,這個接口是Mybatis官方提供的,中文意思是攔截器,所以有可能是通過實現(xiàn)這個攔截器做了某些操作來實現(xiàn)分頁的。
插件
通過代碼追蹤我們看到Interceptor的intercept方法是在Mybatis的一個org.apache.ibatis.plugin.Plugin類的invoke方法中調用的,而這個Plugin類實現(xiàn)了JDK的java.lang.reflect.InvocationHandler接口,這是JDK代理接口。

這個Plugin中有一個wrap方法會返回一個代理類,所以當調用這個代理類的方法時就會走到上面的invoke方法,就可能會進到攔截器的intercept方法。

所以我們看這個warp在哪調的,就知道啥時候創(chuàng)建這個代理類。就是在上面的PageHelper中,再貼一下代碼

攔截器鏈
而這個PageHelper中的plugin方法是實現(xiàn)自Interceptor攔截器接口,所以會有一個地方統(tǒng)一調這個方法,往上追溯就會發(fā)現(xiàn)是在org.apache.ibatis.plugin.InterceptorChain攔截器鏈中調用的,如下。

該類有一個List保存所有攔截器,還有三個方法,分別是pluginAll用于調用所有攔截器的plugin方法,addInterceptor添加攔截器,getInterceptors獲取攔截器鏈。
看到這大致明白了它的原理,PageHelper通過實現(xiàn)Mybatis的Interceptor接口實現(xiàn)分頁,Mybatis通過InterceptorChain調用所有Interceptor。
加載&調用攔截器
那么我們看看Mybatis的攔截器是什么時候添加到攔截器鏈,什么時候被調用的。
通過代碼追溯,發(fā)現(xiàn)在Configuration的addInterceptor方法中調用添加方法,Configuration.addInterceptor是在XMLConfigBuilder的pluginElement方法中被調用

?而XMLConfigBuilder是解析XML方式的Mybatis的配置的,顧名思義pluginElement方法是解析XML中plugin相關的配置節(jié)點的

而我們確實在XML中配置了plugin

所以我們現(xiàn)在知道了mybatis的攔截器是在Mybatis解析配置文件時,解析plugins節(jié)點時添加到InterceptorChain中的。
攔截器什么時候調用的。我們看InterceptorChain的pluginAll方法在哪調的,通過代碼追蹤有如下四個地方調用攔截器鏈

@Intercepts注解
而PageHelper這個攔截器,我們可以發(fā)現(xiàn)這個類上有一個@Intercepts注解,這個注解接收的值為@Signature注解,在Signature注解配置了,Executor.class,query還有四個class:MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class。

了解Mybatis的插件機制的就明白了,這一行配置的意思是攔截Executor中的query方法,方法參數(shù)列表類型是MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class,就是下面這個方法。

所以看到這,我們可以斷定InterceptorChain的pluginAll方法在上述調用點的第四個,也就是org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType),可想而知newExecutor,也就是創(chuàng)建Executor實例,可以斷定,創(chuàng)建Executor時通過PageHelper的plugin方法包裝了Executor,返回的是Executor的代理類。下面會講到創(chuàng)建動態(tài)代理。

通過PageHelper創(chuàng)建代理對象
我們在正向回顧一下,如何調到PageHelper的。首先進入到pluginAll

然后會調到PageHelper的plugin方法,內(nèi)部又調Plugin的warp方法

我們看看Plugin.wrap方法干了啥,代碼如下。代碼跟過來我們知道現(xiàn)在的target是Executor,interceptor是PageHelper。首先獲取PageHelper攔截信息,然后篩選target是否是需要攔截的類型,這里會進入if判斷邏輯,返回Executor的代理對象。

所以這時創(chuàng)建的Executor實例是代理對象,那么就會在某個時候調用代理的invoke方法(org.apache.ibatis.plugin.Plugin#invoke),invoke調Interceptor攔截器的intercept方法,從而調PageHelper的intercept方法執(zhí)行分頁邏輯
org.apache.ibatis.plugin.Plugin#invoke ==》 com.github.pagehelper.PageHelper#intercept
攔截器的調用源頭-動態(tài)代理
因為返回的是Executor的動態(tài)代理,所以肯定是調用Executor的某個方法時觸發(fā)進到invoke方法,具體在哪調的不好找。我們通過打斷點的方式看是從哪進invoke方法的,首先斷點打到Plugin的invoke方法內(nèi)

通過調用棧看到是org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)調過來的,在invoke方法內(nèi)判斷了目標方法是不是我們要攔截的方法,因為PageHelper上注解的也是攔截這個方法,所以會進入到Plugin的invoke方法的第61行。所以就會進入到PageHelper的intercept方法,執(zhí)行具體的攔截邏輯。
分頁邏輯
思路就是拼SQL。
通過代碼跟蹤,最終的分頁邏輯是在com.github.pagehelper.util.SqlUtil#doIntercept方法中,第162行,獲取分頁SQL,
調用
com.github.pagehelper.dialect.AbstractDialect#getPageSql(org.apache.ibatis.mapping.MappedStatement, org.apache.ibatis.mapping.BoundSql, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.cache.CacheKey)

在com.github.pagehelper.dialect.AbstractDialect#getPageSql(org.apache.ibatis.mapping.MappedStatement, org.apache.ibatis.mapping.BoundSql, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.cache.CacheKey)中調用com.github.pagehelper.dialect.AbstractDialect#getPageSql(java.lang.String, com.github.pagehelper.Page, org.apache.ibatis.session.RowBounds,org.apache.ibatis.cache.CacheKey),是一個抽象方法,具體實現(xiàn)有好多種

我們看mysql的,在原始SQL 拼接了" limit ?,?"

?總結
以上是PageHelper實現(xiàn)分頁的原理,總結一下:
Mybatis在四個地方留了擴展點,可以通過自定義攔截器實現(xiàn)Interceptor接口的plugin方法,執(zhí)行自定義邏輯,可以通過該方法對Executor、ParameterHandler、ResultSetHandler、StatementHandler四個對象進行增強、擴展。
PageHelper實現(xiàn)了Interceptor接口,它的plugin方法調用Plugin.wrap方法對目標對象進行包裝,包裝成一個代理對象并返回,代理類的實現(xiàn)就是Plugin自身。
Plugin.wrap方法判斷目標對象是否需要返回代理對象,判斷依據(jù)是:Interceptor實現(xiàn)類(這里是PageHelper)上注解標注的類是否包含目標對象所屬類。這里PageHelper上注解標注參數(shù)為Executor對象,所以創(chuàng)建Executor對象會返回代理對象。
當調用Executor對象的方法時會進入到Plugin.invoke方法。invoke方法會判斷是否需要走攔截器的intercept方法,判斷方式是取攔截器上的注解標注的方法,這里PageHelper標注的為executor.query(四個參數(shù)的那個),所以調這個時才會被攔截器攔截,其余方法還用原始對象調用。
PageHelper的intercept方法調用SqlUtil的intercept方法最終調SqlUtil.doIntercept方法。在這個方法里會執(zhí)行count語句,并將結果放到page對象里,然后判斷需要分頁,則將分頁sql拼在原始sql上,然后執(zhí)行。
簡單來說就是通過mybatis的攔截器和插件實現(xiàn)的,PageHelper實現(xiàn)了Interceptor攔截器接口,并攔截Executor的query方法,在執(zhí)行前PageHelper會在原始SQL前拼裝分頁相關的SQL。
PageHelper支持以下數(shù)據(jù)庫的分頁:Db2、Hsqldbt 、Informix、MySq、Oracle 、SqlServer2012、SqlServer
mybatis的插件Plugin是通過JDK動態(tài)代理對目標對象進行增強
到此這篇關于Mybatis第三方PageHelper分頁插件使用與原理的文章就介紹到這了,更多相關Mybatis第三方PageHelper分頁插件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
java的main方法中調用spring的service方式
這篇文章主要介紹了在java的main方法中調用spring的service方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
Spring Cache和EhCache實現(xiàn)緩存管理方式
這篇文章主要介紹了Spring Cache和EhCache實現(xiàn)緩存管理方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06
解決SpringBoot jar包中的文件讀取問題實現(xiàn)
這篇文章主要介紹了解決SpringBoot jar包中的文件讀取問題實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-08-08

