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語句拼接了分頁的語句,從而實現了從數據庫中分頁獲取的過程。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/250102.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/250102.shtml
英文地址,請注明出處:http://en.pswp.cn/news/250102.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

10-多寫一個@Autowired導致程序崩了

再是javaweb實驗六中&#xff0c;是讓我們改代碼&#xff0c;讓它跑起來&#xff0c;結果我少注釋了一個&#xff0c;導致一直報錯&#xff0c;檢查許久沒有找到&#xff0c;最后通過代碼替換逐步查找&#xff0c;才發現問題。 轉載于:https://www.cnblogs.com/zhumengdexiaoba…

Java class不分32位和64位

1、32位JDK編譯的java class在32位系統和64位系統下都可以運行&#xff0c;64位系統兼容32位程序&#xff0c;可以理解。2、無論是Linux還是Windows平臺下的JDK編譯的java class在Linux、Windows平臺下通用&#xff0c;Java跨平臺特性。3、64位JDK編譯的java class在32位的系統…

包裝對象

原文地址&#xff1a;https://wangdoc.com/javascript/ 定義 對象是JavaScript語言最主要的數據類型&#xff0c;三種原始類型的值--數值、字符串、布爾值--在一定條件下&#xff0c;也會自動轉為對象&#xff0c;也就是原始類型的包裝對象。所謂包裝對象&#xff0c;就是分別與…

[C++] 轉義序列

參考 C Primer(第5版)P36 名稱轉義序列換行符\n橫向制表符\t報警(響鈴)符\a縱向制表符\v退格符\b雙引號"反斜杠\問號?單引號’回車符\r進紙符\f

vue使用(二)

本節目標&#xff1a; 1.數據路徑的三種方式 2.{{}}和v-html的區別 1.綁定圖片的路徑 方法一&#xff1a;直接寫路徑 <img src"http://pic.baike.soso.com/p/20140109/20140109142534-188809525.jpg"> 方法二&#xff1a;在data中寫路徑&#xff0c;在…

typedef 為類型取別名

#include <stdio.h> int main() {   typedef int myint; // 為int 類型取自己想要的名字   myint a 10;   printf("%d", a);   return 0;} 其他類型的用法也是一樣的 typedef 類型 自己想要取得名字; 轉載于:https://www.cnblogs.com/hello-dummy/p/9…

【C++】如何提高Cache的命中率,示例

參考鏈接 https://stackoverflow.com/questions/16699247/what-is-a-cache-friendly-code 只是堆積&#xff1a;緩存不友好與緩存友好代碼的典型例子是矩陣乘法的“緩存阻塞”。 樸素矩陣乘法看起來像 for(i0;i<N;i) {for(j0;j<N;j) {dest[i][j] 0;for( k;k<N;i)…

springboot---整合redis

pom.xml新增 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>代碼結構如下 其中redis.yml是連接redis的配置文件&#xff0c;RedisConfig.java是java配置…

[Head First Java] - 簡單的建議程序

參考 - p481、p484 與我對接的業務層使用的是JAVA語言,因此花點時間入門java.下面幾篇博客可能都是關于java的,我覺得在工作中可能會遇到的 簡單的通信 DailyAdviceClient(客戶端程序) import java.io.*; import java.net.*;public class DailyAdviceClient{public void go()…

SQL重復記錄查詢的幾種方法

1 查找表中多余的重復記錄&#xff0c;重復記錄是根據單個字段1 select * from TB_MAT_BasicData1 2 where MATNR in ( select MATNR from TB_MAT_BasicData1 group by MATNR having count(MATNR)>1) 2.表需要刪除重復的記錄&#xff08;重復記錄保留1條&#xff09;&…

Redis 的應用場景

之前講過Redis的介紹&#xff0c;及使用Redis帶來的優勢&#xff0c;這章整理了一下Redis的應用場景&#xff0c;也是非常重要的&#xff0c;學不學得好&#xff0c;能正常落地是關鍵。 下面一一來分析下Redis的應用場景都有哪些。 1、緩存 緩存現在幾乎是所有中大型網站都在…

[Head First Java] - Swing做一個簡單的客戶端

參考 - P487 1. vscode配置java的格式 點擊左下角齒輪 -> 設置 -> 打開任意的setting.json輸入如下代碼 {code-runner.executorMap": {"java": "cd $dir && javac -encoding utf-8 $fileName && java $fileNameWithoutExt"},…

【Nginx】 Nginx實現端口轉發

什么是端口轉發 當我們在服務器上搭建一個圖書以及一個電影的應用&#xff0c;其中圖書應用啟動了 8001 端口&#xff0c;電影應用啟動了 8002 端口。此時如果我們可以通過 localhost:8001 //圖書 localhost:8002 //電影 但我們一般訪問應用的時候都是希望不加端口就訪問…

計算機網絡知識總結

一 OSI與TCP/IP各層的結構與功能&#xff0c;都有哪些協議 OSI的七層體系結構概念清楚&#xff0c;理論也很完整&#xff0c;但是它比較復雜而且不實用。在這里順帶提一下之前一直被一些大公司甚至一些國家政府支持的OSI失敗的原因&#xff1a; OSI的專家缺乏實際經驗&#xff…

使用redis做為MySQL的緩存

介紹 在實際項目中&#xff0c;MySQL數據庫服務器有時會位于另外一臺主機&#xff0c;需要通過網絡來訪問數據庫&#xff1b;即使應用程序與MySQL數據庫在同一個主機中&#xff0c;訪問MySQL也涉及到磁盤IO操作&#xff08;MySQL也有一些數據預讀技術&#xff0c;能夠減少磁盤I…

[Head First Java] - 給線程命名

參考 - P503 public class RunThreads implements Runnable {public static void main (String[] args) {RunThreads runner new RunThreads();Thread alpha new Thread(runner);Thread beta new Thread(runner);alpha.setName("Alpha thread");beta.setName(&qu…

Cortex-M3 的SVC、PendSV異常,與操作系統(ucos實時系統)(轉)

Cortex-M3 的SVC、PendSV異常&#xff0c;與操作系統(ucos實時系統)轉載于:https://www.cnblogs.com/LittleTiger/p/10070824.html

快速排序的C++版

int Partition(int a[], int low, int high) {int x a[high];//將輸入數組的最后一個數作為主元&#xff0c;用它來對數組進行劃分int i low - 1;//i是最后一個小于主元的數的下標for (int j low; j < high; j)//遍歷下標由low到high-1的數{if (a[j] < x)//如果數小于…

springboot---整合shiro

Shiro是一個非常不錯的權限框架&#xff0c;它提供了登錄和權限驗證功能 1.創建數據庫腳本 SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0; -- ---------------------------- -- Table structure for module -- ---------------------------- DROP TABLE IF EXISTS module; C…

[Head First Java] - 線程共享數據問題

參考 - P507 1. 說明 兩個線程共享同一份數據,每次使用數據時,需要先判斷其是否在合理范圍每次使用數據完畢使用Thread.sleep函數讓線程阻塞 2.代碼 class BankAccount {private int balance 100;public int getBalance() {return balance;}public void withdraw(int amou…