日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

PageHelper工作原理

發布時間:2023/12/10 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 PageHelper工作原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

數據分頁功能是我們軟件系統中必備的功能,在持久層使用mybatis的情況下,pageHelper來實現后臺分頁則是我們常用的一個選擇,所以本文專門類介紹下。

PageHelper原理
相關依賴

<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.2.8</version> </dependency> <dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>1.2.15</version> </dependency>

1.添加plugin
??要使用PageHelper首先在mybatis的全局配置文件中配置。如下:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration><plugins><!-- com.github.pagehelper為PageHelper類所在包名 --><plugin interceptor="com.github.pagehelper.PageHelper"><property name="dialect" value="mysql" /><!-- 該參數默認為false --><!-- 設置為true時,會將RowBounds第一個參數offset當成pageNum頁碼使用 --><!-- 和startPage中的pageNum效果一樣 --><property name="offsetAsPageNum" value="true" /><!-- 該參數默認為false --><!-- 設置為true時,使用RowBounds分頁會進行count查詢 --><property name="rowBoundsWithCount" value="true" /><!-- 設置為true時,如果pageSize=0或者RowBounds.limit = 0就會查詢出全部的結果 --><!-- (相當于沒有執行分頁查詢,但是返回結果仍然是Page類型) --><property name="pageSizeZero" value="true" /><!-- 3.3.0版本可用 - 分頁參數合理化,默認false禁用 --><!-- 啟用合理化時,如果pageNum<1會查詢第一頁,如果pageNum>pages會查詢最后一頁 --><!-- 禁用合理化時,如果pageNum<1或pageNum>pages會返回空數據 --><property name="reasonable" value="false" /><!-- 3.5.0版本可用 - 為了支持startPage(Object params)方法 --><!-- 增加了一個`params`參數來配置參數映射,用于從Map或ServletRequest中取值 --><!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默認值 --><!-- 不理解該含義的前提下,不要隨便復制該配置 --><property name="params" value="pageNum=start;pageSize=limit;" /><!-- always總是返回PageInfo類型,check檢查返回類型是否為PageInfo,none返回Page --><property name="returnPageInfo" value="check" /></plugin></plugins> </configuration>

2.加載過程
??我們通過如下幾行代碼來演示過程

// 獲取配置文件 InputStream inputStream = Resources.getResourceAsStream("mybatis/mybatis-config.xml"); // 通過加載配置文件獲取SqlSessionFactory對象 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); // 獲取SqlSession對象 SqlSession session = factory.openSession(); PageHelper.startPage(1, 5); session.selectList("com.bobo.UserMapper.query");

加載配置文件我們從這行代碼開始

new SqlSessionFactoryBuilder().build(inputStream);public SqlSessionFactory build(InputStream inputStream) {return build(inputStream, null, null);}



private void pluginElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {// 獲取到內容:com.github.pagehelper.PageHelperString interceptor = child.getStringAttribute("interceptor");// 獲取配置的屬性信息Properties properties = child.getChildrenAsProperties();// 創建的攔截器實例Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();// 將屬性和攔截器綁定interceptorInstance.setProperties(properties);// 這個方法需要進入查看configuration.addInterceptor(interceptorInstance);}} }public void addInterceptor(Interceptor interceptor) {// 將攔截器添加到了 攔截器鏈中 而攔截器鏈本質上就是一個List有序集合interceptorChain.addInterceptor(interceptor);}

小結:通過SqlSessionFactory對象的獲取,我們加載了全局配置文件及映射文件同時還將配置的攔截器添加到了攔截器鏈中。

3.PageHelper定義的攔截信息
??我們來看下PageHelper的源代碼的頭部定義

@SuppressWarnings("rawtypes") @Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})) public class PageHelper implements Interceptor {//sql工具類private SqlUtil sqlUtil;//屬性參數信息private Properties properties;//配置對象方式private SqlUtilConfig sqlUtilConfig;//自動獲取dialect,如果沒有setProperties或setSqlUtilConfig,也可以正常進行private boolean autoDialect = true;//運行時自動獲取dialectprivate boolean autoRuntimeDialect;//多數據源時,獲取jdbcurl后是否關閉數據源private boolean closeConn = true; // 定義的是攔截 Executor對象中的 // query(MappedStatement ms,Object o,RowBounds ob ResultHandler rh) // 這個方法 type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))

PageHelper中已經定義了該攔截器攔截的方法是什么。

4.Executor
??接下來我們需要分析下SqlSession的實例化過程中Executor發生了什么。我們需要從這行代碼開始跟蹤

SqlSession session = factory.openSession(); public SqlSession openSession() {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); }





增強Executor


??到此我們明白了,Executor對象其實被我們生存的代理類增強了。invoke的代碼為

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {Set<Method> methods = signatureMap.get(method.getDeclaringClass());// 如果是定義的攔截的方法 就執行intercept方法if (methods != null && methods.contains(method)) {// 進入查看 該方法增強return interceptor.intercept(new Invocation(target, method, args));}// 不是需要攔截的方法 直接執行return method.invoke(target, args);} catch (Exception e) {throw ExceptionUtil.unwrapThrowable(e);} } /*** Mybatis攔截器方法** @param invocation 攔截器入參* @return 返回執行結果* @throws Throwable 拋出異常*/ public Object intercept(Invocation invocation) throws Throwable {if (autoRuntimeDialect) {SqlUtil sqlUtil = getSqlUtil(invocation);return sqlUtil.processPage(invocation);} else {if (autoDialect) {initSqlUtil(invocation);}return sqlUtil.processPage(invocation);} }

該方法中的內容我們后面再分析。Executor的分析我們到此,接下來看下PageHelper實現分頁的具體過程。

5.分頁過程
??接下來我們通過代碼跟蹤來看下具體的分頁流程,我們需要分別從兩行代碼開始:

5.1 startPage
PageHelper.startPage(1, 5);

/*** 開始分頁** @param params*/ public static <E> Page<E> startPage(Object params) {Page<E> page = SqlUtil.getPageFromObject(params);//當已經執行過orderBy的時候Page<E> oldPage = SqlUtil.getLocalPage();if (oldPage != null && oldPage.isOrderByOnly()) {page.setOrderBy(oldPage.getOrderBy());}SqlUtil.setLocalPage(page);return page; } /*** 開始分頁** @param pageNum 頁碼* @param pageSize 每頁顯示數量* @param count 是否進行count查詢* @param reasonable 分頁合理化,null時用默認配置*/ public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable) {return startPage(pageNum, pageSize, count, reasonable, null); } /*** 開始分頁** @param offset 頁碼* @param limit 每頁顯示數量* @param count 是否進行count查詢*/ public static <E> Page<E> offsetPage(int offset, int limit, boolean count) {Page<E> page = new Page<E>(new int[]{offset, limit}, count);//當已經執行過orderBy的時候Page<E> oldPage = SqlUtil.getLocalPage();if (oldPage != null && oldPage.isOrderByOnly()) {page.setOrderBy(oldPage.getOrderBy());}// 這是重點!!!SqlUtil.setLocalPage(page);return page; } private static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>(); // 將分頁信息保存在ThreadLocal中 線程安全! public static void setLocalPage(Page page) {LOCAL_PAGE.set(page); }

5.2selectList方法

session.selectList("com.bobo.UserMapper.query"); public <E> List<E> selectList(String statement) {return this.selectList(statement, null); }public <E> List<E> selectList(String statement, Object parameter) {return this.selectList(statement, parameter, RowBounds.DEFAULT); }


我們需要回到invoke方法中繼續看

/*** Mybatis攔截器方法** @param invocation 攔截器入參* @return 返回執行結果* @throws Throwable 拋出異常*/ public Object intercept(Invocation invocation) throws Throwable {if (autoRuntimeDialect) {SqlUtil sqlUtil = getSqlUtil(invocation);return sqlUtil.processPage(invocation);} else {if (autoDialect) {initSqlUtil(invocation);}return sqlUtil.processPage(invocation);} }

進入sqlUtil.processPage(invocation);方法

/*** Mybatis攔截器方法** @param invocation 攔截器入參* @return 返回執行結果* @throws Throwable 拋出異常*/ private Object _processPage(Invocation invocation) throws Throwable {final Object[] args = invocation.getArgs();Page page = null;//支持方法參數時,會先嘗試獲取Pageif (supportMethodsArguments) {// 從線程本地變量中獲取Page信息,就是我們剛剛設置的page = getPage(args);}//分頁信息RowBounds rowBounds = (RowBounds) args[2];//支持方法參數時,如果page == null就說明沒有分頁條件,不需要分頁查詢if ((supportMethodsArguments && page == null)//當不支持分頁參數時,判斷LocalPage和RowBounds判斷是否需要分頁|| (!supportMethodsArguments && SqlUtil.getLocalPage() == null && rowBounds == RowBounds.DEFAULT)) {return invocation.proceed();} else {//不支持分頁參數時,page==null,這里需要獲取if (!supportMethodsArguments && page == null) {page = getPage(args);}// 進入查看return doProcessPage(invocation, page, args);} } /*** Mybatis攔截器方法** @param invocation 攔截器入參* @return 返回執行結果* @throws Throwable 拋出異常*/private Page doProcessPage(Invocation invocation, Page page, Object[] args) throws Throwable {//保存RowBounds狀態RowBounds rowBounds = (RowBounds) args[2];//獲取原始的msMappedStatement ms = (MappedStatement) args[0];//判斷并處理為PageSqlSourceif (!isPageSqlSource(ms)) {processMappedStatement(ms);}//設置當前的parser,后面每次使用前都會set,ThreadLocal的值不會產生不良影響((PageSqlSource)ms.getSqlSource()).setParser(parser);try {//忽略RowBounds-否則會進行Mybatis自帶的內存分頁args[2] = RowBounds.DEFAULT;//如果只進行排序 或 pageSizeZero的判斷if (isQueryOnly(page)) {return doQueryOnly(page, invocation);}//簡單的通過total的值來判斷是否進行count查詢if (page.isCount()) {page.setCountSignal(Boolean.TRUE);//替換MSargs[0] = msCountMap.get(ms.getId());//查詢總數Object result = invocation.proceed();//還原msargs[0] = ms;//設置總數page.setTotal((Integer) ((List) result).get(0));if (page.getTotal() == 0) {return page;}} else {page.setTotal(-1l);}//pageSize>0的時候執行分頁查詢,pageSize<=0的時候不執行相當于可能只返回了一個countif (page.getPageSize() > 0 &&((rowBounds == RowBounds.DEFAULT && page.getPageNum() > 0)|| rowBounds != RowBounds.DEFAULT)) {//將參數中的MappedStatement替換為新的qspage.setCountSignal(null);// 重點是查看該方法BoundSql boundSql = ms.getBoundSql(args[1]);args[1] = parser.setPageParameter(ms, args[1], boundSql, page);page.setCountSignal(Boolean.FALSE);//執行分頁查詢Object result = invocation.proceed();//得到處理結果page.addAll((List) result);}} finally {((PageSqlSource)ms.getSqlSource()).removeParser();}//返回結果return page;} 進入 BoundSql boundSql = ms.getBoundSql(args[1])方法跟蹤到PageStaticSqlSource類中的@Override protected BoundSql getPageBoundSql(Object parameterObject) {String tempSql = sql;String orderBy = PageHelper.getOrderBy();if (orderBy != null) {tempSql = OrderByParser.converToOrderBySql(sql, orderBy);}tempSql = localParser.get().getPageSql(tempSql);return new BoundSql(configuration, tempSql, localParser.get().getPageParameterMapping(configuration, original.getBoundSql(parameterObject)), parameterObject); } @Override protected BoundSql getPageBoundSql(Object parameterObject) {String tempSql = sql;String orderBy = PageHelper.getOrderBy();if (orderBy != null) {tempSql = OrderByParser.converToOrderBySql(sql, orderBy);}tempSql = localParser.get().getPageSql(tempSql);return new BoundSql(configuration, tempSql, localParser.get().getPageParameterMapping(configuration, original.getBoundSql(parameterObject)), parameterObject); }



也可以看Oracle的分頁實現

至此我們發現PageHelper分頁的實現原來是在我們執行SQL語句之前動態的將SQL語句拼接了分頁的語句,從而實現了從數據庫中分頁獲取的過程。

總結

以上是生活随笔為你收集整理的PageHelper工作原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。