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.mzph.cn/news/250102.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

10-多写一个@Autowired导致程序崩了

再是javaweb实验六中&#xff0c;是让我们改代码&#xff0c;让它跑起来&#xff0c;结果我少注释了一个&#xff0c;导致一直报错&#xff0c;检查许久没有找到&#xff0c;最后通过代码替换逐步查找&#xff0c;才发现问题。 转载于:https://www.cnblogs.com/zhumengdexiaoba…

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] - Swing做一个简单的客户端

参考 - P487 1. vscode配置java的格式 点击左下角齿轮 -> 设置 -> 打开任意的setting.json输入如下代码 {code-runner.executorMap": {"java": "cd $dir && javac -encoding utf-8 $fileName && java $fileNameWithoutExt"},…

计算机网络知识总结

一 OSI与TCP/IP各层的结构与功能&#xff0c;都有哪些协议 OSI的七层体系结构概念清楚&#xff0c;理论也很完整&#xff0c;但是它比较复杂而且不实用。在这里顺带提一下之前一直被一些大公司甚至一些国家政府支持的OSI失败的原因&#xff1a; OSI的专家缺乏实际经验&#xff…

[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…

快速排序的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)//如果数小于…

asp.net中提交表单数据时提示从客户端(。。。)中检测到有潜在危险的 Request.Form 值...

看到这个图是不是很亲切熟悉哈&#xff0c;做过。net的肯定都见过哈 已经 将近4年没碰。net了&#xff0c;今天正好朋友的程序有几个bug,让我帮忙修复下&#xff0c;于是我就抱着试试看的心情改了改&#xff0c;改到最后一个问题的时候也就是上面的这个问题&#xff0c;我一看&…

Shiro表结构设计

表设计 开发用户-角色-权限管理系统&#xff0c;首先我们需要知道用户-角色-权限管理系统的表结构设计。 在用户-角色-权限管理系统找那个一般会涉及5张表&#xff0c;分别为&#xff1a; 1.sys_users用户表 2.sys_roles角色表 3.sys_permissions权限表&#xff08;或资源表&…

[Java核心技术(卷I)] - 简易的日历

参考 - P102~P103 1. 目标 生成一个日历,格式如下图所示。 ps: 当前的天数需要标记为* 2. 核心 对日历的变量 import java.time.*; public class CalendarTest{public static void main(String[] args) {LocalDate date LocalDate.now(); // 获取当前日期int month date…

个人作业——福大微信公众号使用评测

案例分析&#xff1a;在福州大学公众号上&#xff0c;我们可以即时使用手机关注福大新闻&#xff0c;查看自身课表、成绩等。公众号可能存在一些小bug影响同学们的用户体验。本次作业中&#xff0c;作为一个用户——福大的学生&#xff0c;将切身体验该公众号的功能&#xff0c…

在winform中使用wpf窗体

在winform项目&#xff0c;通过引用dll可以添加WPF窗体&#xff0c;如下 但是如果直接在winform的项目中添加wpf窗体还是有部分问题&#xff0c;图片的显示。 直接在XAML界面中用Source属性设置图片会出现错误。必须通过后台代码的方式来实现。 image1.Source GetImageIcon(gl…

shiro---注解

RequiresAuthentication 验证用户是否登录&#xff0c;等同于方法subject.isAuthenticated() 结果为true时。 RequiresUser 验证用户是否被记忆&#xff0c;user有两种含义&#xff1a; 一种是成功登录的&#xff08;subject.isAuthenticated() 结果为true&#xff09;&…

[Java核心技术(卷I)] - Java中的参数能做什么和不能做什么

1. 参考 - P123 ~ P126 2. 你将学到 Java中对方法参数能做什么和不能做什么 方法不能修改基本数据类型的参数(数值型或布尔型)方法可以改变对象参数的状态方法不能让一个对象参数引用一个新的对象 3. 代码证明 public class ParamTest {public static void main(String[] ar…

软件构造 第五章第一节 可复用性的度量、形态和外部观察

第五章第一节 可复用性的度量、形态和外部观察 面向复用编程(programming for reuse)&#xff1a;开发出可复用的软件 基于复用编程(programming with reuse)&#xff1a;利用已有的可复用软件搭建应用系统 代码复用的类型&#xff1a; 白盒复用&#xff1a;源代码可见&#x…

[web性能优化] - 使用在线工具对html、js、css进行压缩

参考 1. 学习点 使用 在线工具对html、css、js进行压缩学会分析压缩前后的效率提高点 2. 解决方案: 2.1 HTML压缩 在线压缩nodejs提供了 html-minifier工具(在构建层对代码进行压缩)后端模板引擎渲染压缩 2.2 CSS压缩 使用html-minifier对html中的css进行压缩使用clean-cs…

SpringBoot之基础

简介 背景 J2EE笨重的开发 / 繁多的配置 / 低下的开发效率 / 复杂的部署流程 / 第三方技术集成难度大 特点 ① 快速创建独立运行的spring项目以及主流框架集成 ② 使用嵌入式的Servlet容器, 应用无需达成war包 ③ starters自动依赖和版本控制 ④ 大量自动配置, 简化开发, 也可修…

[Java核心技术(卷I)] - vscode手动编译运行继承类

参考 - P160~P161 主要有3个类: 一个测试类(ManagerTest)、一个子类(Manager)、一个父类(Employee) 注意点: -1. 使用 javac -d . *.java进行预编译 目录结构入下: 此时会生成目录结构如下: 之后运行 java com.inheritance.ManagerTest 附上几个类的代码 // com.inhe…

【Social Listening实战】当数据分析遭遇心理动力学:用户深层次的情感需求浮出水面...

本文转自知乎 作者&#xff1a;苏格兰折耳喵 ————————————————————————————————————————————————————— 本文篇幅较长&#xff0c;分为五部分&#xff0c;在中间部分有关于心理分析工具的介绍&#xff0c;案例分散在第二部…

Oracle 11g DG主库节点2 ORA-00245: control file backup fail

--节点1报错 Sun Dec 09 08:29:57 2018Control file backup creation failed: failure to open backup target file /u01/app/oracle/product/11.2.0/db_1/dbs/snapcf_zwdb.ctl.Errors in file /u01/app/oracle/diag/rdbms/zwdb/zwdb2/trace/zwdb2_arc0_167660.trc:ORA-27037: …

hive字符函数

转载于:https://www.cnblogs.com/ggzhangxiaochao/p/9222732.html