PageHelper在SpringBoot中的使用和原理分析

news/2024/9/19 3:29:28/文章来源:https://www.cnblogs.com/Linwei33/p/18417484

PageHelper在SpringBoot中的使用和原理分析

在SpringBoot项目中使用Mybatis的PageHelper分页插件进行分页查询

1、导入相关依赖

<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope>
</dependency>
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.2</version>
</dependency>
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>2.1.0</version>
</dependency>

2、添加相关配置信息

spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: rooturl: jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=truepagehelper:reasonable: truedefaultCount: true # 分页插件默认参数支持 default-count 形式,自定义扩展的参数,必须大小写一致helperDialect: mysql
mybatis:mapper-locations: classpath:/mapper/*.xml

3、编写mapper接口

@Repository
public interface UserMapper {List<User> getAllUser();
}

4、编写service,其中getAllUser()是普通查询,getAllUser1()和getAllUser2()是分页查询,使用PageHelper需要传入pageNum和pageSize

@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public List<User> getAllUser(){return userMapper.getAllUser();}public List<User> getAllUser1(int pageNum,int pageSize){PageHelper.startPage(pageNum,pageSize);List<User> allUser = userMapper.getAllUser();return allUser;}public PageInfo<User> getAllUser2(int pageNum,int pageSize){PageHelper.startPage(pageNum,pageSize);List<User> allUser = userMapper.getAllUser();PageInfo<User> pageInfo = new PageInfo<>();pageInfo.setList(allUser);return pageInfo;}
}

5、编写Controller

@RestController
public class UserController {@Autowiredprivate UserService userService;@GetMapping("/users")public List<User> getAllUser(){return userService.getAllUser();}@GetMapping("/users1")public List<User> getAllUser1(){return userService.getAllUser1(1,3);}@GetMapping("/users2")public PageInfo<User> getAllUser2(){return userService.getAllUser2(1,3);}
}

6、编写Mybatis的mapper文件,其中namespace要与接口方法所在的包绑定,MappedStatement的id要与接口方法绑定

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper"><!-- 通用查询映射结果 --><resultMap id="BaseResultMap" type="com.example.demo.pojo.User"><id column="id" property="id" /><result column="name" property="name" /><result column="phone" property="phone" /></resultMap><select id="getAllUser" resultType="com.example.demo.pojo.User">select * from user</select>
</mapper>

7、测试程序,测试接口

接口:http://localhost:8080/users

[{"id":"1","name":"zlw","phone":"17860397215"},{"id":"2","name":"zyx","phone":"18865986031"},{"id":"3","name":"zq","phone":"17860397216"},{"id":"4","name":"zxj","phone":"17860398476"},{"id":"5","name":"zmj","phone":"15021469872"},{"id":"6","name":"zqq","phone":"17652369421"}]

接口:http://localhost:8080/users1

[{"id":"1","name":"zlw","phone":"17860397215"},{"id":"2","name":"zyx","phone":"18865986031"},{"id":"3","name":"zq","phone":"17860397216"}]

接口:http://localhost:8080/users2

{"total":0,"list":[{"id":"1","name":"zlw","phone":"17860397215"},{"id":"2","name":"zyx","phone":"18865986031"},{"id":"3","name":"zq","phone":"17860397216"}],"pageNum":0,"pageSize":0,"size":0,"startRow":0,"endRow":0,"pages":0,"prePage":0,"nextPage":0,"isFirstPage":false,"isLastPage":false,"hasPreviousPage":false,"hasNextPage":false,"navigatePages":0,"navigatepageNums":null,"navigateFirstPage":0,"navigateLastPage":0}

接下来从代码的角度看看PageHelper是怎么实现分页的。

当导入pagehelper-spring-boot-starter依赖后,pagehelper已经自定了一个自动配置类PageHelperAutoConfiguration,可以在maven包下找到对应的spring.factories文件看一下。

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration

进入这个类看一下

@Configuration
@ConditionalOnBean({SqlSessionFactory.class})
@EnableConfigurationProperties({PageHelperProperties.class, PageHelperStandardProperties.class})
@AutoConfigureAfter({MybatisAutoConfiguration.class})
@Lazy(false)
public class PageHelperAutoConfiguration implements InitializingBean {private final List<SqlSessionFactory> sqlSessionFactoryList;private final PageHelperProperties properties;public PageHelperAutoConfiguration(List<SqlSessionFactory> sqlSessionFactoryList, PageHelperStandardProperties standardProperties) {this.sqlSessionFactoryList = sqlSessionFactoryList;this.properties = standardProperties.getProperties();}public void afterPropertiesSet() throws Exception {PageInterceptor interceptor = new PageInterceptor();interceptor.setProperties(this.properties);Iterator var2 = this.sqlSessionFactoryList.iterator();while(var2.hasNext()) {SqlSessionFactory sqlSessionFactory = (SqlSessionFactory)var2.next();org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration();if (!this.containsInterceptor(configuration, interceptor)) {configuration.addInterceptor(interceptor);}}}private boolean containsInterceptor(org.apache.ibatis.session.Configuration configuration, Interceptor interceptor) {try {return configuration.getInterceptors().stream().anyMatch((config) -> {return interceptor.getClass().isAssignableFrom(config.getClass());});} catch (Exception var4) {return false;}}
}

可以看到这个自动配置类PageHelperAutoConfiguration实现了InitializingBean接口并且重写了afterPropertiesSet()方法,所以当spring在初始化PageHelperAutoConfiguration时会执行afterPropertiesSet()进行初始化。这里创建了一个PageInterceptor分页拦截器,然后将PageInterceptor配置到Mybatis中。

这个PageInterceptor是实现分页的关键,点进去看看,先看一下类上的注解。

@Intercepts({@Signature(type = Executor.class,method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
), @Signature(type = Executor.class,method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
)})

@Intercepts表明PageInterceptor是一个拦截器,@Signature定义了这了拦截器要拦截的类型,method = "query"也就是这个拦截器会拦截所有的sql查询方法。

回到UserService.java,在调用mapper查询的代码上打个断点看一下PageInterceptor是怎么执行的。

@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public List<User> getAllUser(){return userMapper.getAllUser();}public List<User> getAllUser1(int pageNum,int pageSize){PageHelper.startPage(pageNum, pageSize);List<User> allUser = userMapper.getAllUser();return allUser;}public PageInfo<User> getAllUser2(int pageNum,int pageSize){PageHelper.startPage(pageNum,pageSize);List<User> allUser = userMapper.getAllUser();PageInfo<User> pageInfo = new PageInfo<>();pageInfo.setList(allUser);return pageInfo;}
}

程序首先会执行PageInterceptor的plugin()方法,它返回了Mybatis执行器的代理对象,跟进wrap()方法看看。

public class PageInterceptor implements Interceptor {//...public Object plugin(Object target) {return Plugin.wrap(target, this);}//...
}
public class Plugin implements InvocationHandler {private final Object target;private final Interceptor interceptor;private final Map<Class<?>, Set<Method>> signatureMap;private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {this.target = target;this.interceptor = interceptor;this.signatureMap = signatureMap;}public static Object wrap(Object target, Interceptor interceptor) {//map的key是Mybatis的执行器,value是要执行拦截的查询方法Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);//target是一个cachingExcutor类,这是Mybatis的执行器,负责执行查询操作和缓存机制Class<?> type = target.getClass();Class<?>[] interfaces = getAllInterfaces(type, signatureMap);//为cachingExcutor生成一个代理对象,看第三个参数,Plugin类自身实现了Invocation接口,这里把自身传进去了return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;}//Plugin类实现Invocation接口,并重写invoke方法public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());//如果方法需要拦截,就执行PageIntercptor的intercept()方法return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);} catch (Exception var5) {throw ExceptionUtil.unwrapThrowable(var5);}}
}

Plugin的wrap()方法最终返回了一个Mybatis执行器的代理对象,在执行分页查询方法时,通过invoke方法进行调用,这时会执行PageInterceptor的intercept方法。

//PageInterceptor.class
public Object intercept(Invocation invocation) throws Throwable {try {Object[] args = invocation.getArgs();MappedStatement ms = (MappedStatement)args[0];Object parameter = args[1];RowBounds rowBounds = (RowBounds)args[2];ResultHandler resultHandler = (ResultHandler)args[3];Executor executor = (Executor)invocation.getTarget();CacheKey cacheKey;BoundSql boundSql;if (args.length == 4) {boundSql = ms.getBoundSql(parameter);cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);} else {cacheKey = (CacheKey)args[4];boundSql = (BoundSql)args[5];}this.checkDialectExists();if (this.dialect instanceof BoundSqlInterceptor.Chain) {boundSql = ((BoundSqlInterceptor.Chain)this.dialect).doBoundSql(Type.ORIGINAL, boundSql, cacheKey);}List resultList;if (!this.dialect.skip(ms, parameter, rowBounds)) {this.debugStackTraceLog();Future<Long> countFuture = null;Long count;if (this.dialect.beforeCount(ms, parameter, rowBounds)) {if (this.dialect.isAsyncCount()) {countFuture = this.asyncCount(ms, boundSql, parameter, rowBounds);} else {count = this.count(executor, ms, parameter, rowBounds, (ResultHandler)null, boundSql);if (!this.dialect.afterCount(count, parameter, rowBounds)) {Object var13 = this.dialect.afterPage(new ArrayList(), parameter, rowBounds);return var13;}}}//分页的重点resultList = ExecutorUtil.pageQuery(this.dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);if (countFuture != null) {count = (Long)countFuture.get();this.dialect.afterCount(count, parameter, rowBounds);}} else {resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);}Object var17 = this.dialect.afterPage(resultList, parameter, rowBounds);return var17;} finally {if (this.dialect != null) {this.dialect.afterAll();}}
}

ExecutorUtil.pageQuery()方法是分页逻辑的重点部分,进去看看。

//ExecutorUtil.class
public static <E> List<E> pageQuery(Dialect dialect, Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql, CacheKey cacheKey) throws SQLException {if (!dialect.beforePage(ms, parameter, rowBounds)) {return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);} else {parameter = dialect.processParameterObject(ms, parameter, boundSql, cacheKey);//从mysql方言中获取分页sql,在原始sql上拼接limitString pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey);//创建一个BoundSql对象,包含了分页sql以及其他的参数BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);Iterator var12 = additionalParameters.keySet().iterator();while(var12.hasNext()) {String key = (String)var12.next();pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));}if (dialect instanceof BoundSqlInterceptor.Chain) {pageBoundSql = ((BoundSqlInterceptor.Chain)dialect).doBoundSql(Type.PAGE_SQL, pageBoundSql, cacheKey);}//执行器去查询return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, pageBoundSql);}
}

从dialect.getPageSql()方法一直跟进可以进行mysql方言的getPageSql()方法,可以看到这个方法在原始的sql上拼接上了limit语句

//MySqlDialect.class
public String getPageSql(String sql, Page page, CacheKey pageKey) {StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);sqlBuilder.append(sql);if (page.getStartRow() == 0L) {sqlBuilder.append("\n LIMIT ? ");} else {sqlBuilder.append("\n LIMIT ?, ? ");}return sqlBuilder.toString();
}

接下来会封装一个BoundSql对象,包含分页sql和一些参数。

image-20240917204434720

取哪些数据是根据pageNum和pageSize来计算的,这些参数是在调用mapper接口的方法之前通过PageHelper的startPage方法传入的。

PageHelper.startPage(pageNum, pageSize);

最后调用执行器的query()方法从数据库中查询结果并将其返回。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/798764.html

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

相关文章

php开启pdo与pdo_mysql扩展模块的方法

在Windows服务器中启用PHP的PDO(PHP Data Objects)和PDO_MySQL扩展模块的方法如下: 步骤 1:定位 php.ini 文件找到 php.ini 文件:通常,php.ini 文件位于PHP安装目录中,例如 C:\xampp\php(如果是XAMPP环境)或者其他PHP安装路径下。 如果不确定 php.ini 文件的位置,可以…

安装php的mysqli扩展

安装和启用PHP的mysqli扩展通常取决于你的操作系统和PHP环境。下面分别介绍在不同环境下的操作方法: Windows 环境找到 php.ini 文件:通常 php.ini 文件位于PHP安装目录中,例如 C:\xampp\php(如果是XAMPP环境)或者其他PHP安装路径下。 如果不确定 php.ini 文件的位置,可以…

易优最低支持php什么版本

根据提供的信息,易优CMS(EyouCMS)的最低支持PHP版本为5.4。这意味着你可以使用PHP 5.4或更高版本来安装和运行易优CMS。不过,官方推荐使用PHP 5.5到5.6之间的版本,这是因为这些版本在性能和稳定性方面表现良好,并且能够很好地兼容易优CMS的功能。 易优CMS的PHP版本支持总…

易优CMS后台如何备份数据库

步骤 1:进入后台登录易优CMS后台。 在后台左侧菜单栏中找到“功能地图”(低版本的程序点击“更多功能”)。步骤 2:进入备份还原功能在“功能地图”中找到“备份还原”功能,并点击进入。步骤 3:进行数据备份在“备份还原”页面中,点击“数据备份”。 等待一段时间,直到备…

易优CMS网站迁移提示数据库版本不一致

当你在迁移EyouCMS网站时遇到“数据库版本不一致”的提示,通常是因为目标数据库的版本与EyouCMS所需的版本不一致。为了解决这个问题,可以采取以下几个步骤: 1. 确认数据库版本 首先,确认你的目标数据库版本是否符合EyouCMS的要求。EyouCMS通常支持MySQL 5.6及以上版本。检…

php开启file_put_contents函数的支持

file_put_contents 是 PHP 中的一个内置函数,用于将字符串写入到文件中。如果这个函数不可用,通常是因为 PHP 的运行环境配置问题,或者是文件系统的权限设置问题。这里有一些可能的原因和解决方案: 原因分析文件权限问题:文件或文件夹的权限设置不正确,导致 PHP 脚本无法…

eyoucms易优无法安装,提示当前数据库结构与官方不一致

当你在安装易优CMS(EyouCMS)时遇到“当前数据库结构与官方不一致”的提示,这通常意味着你的数据库版本或结构与CMS所需的版本或结构不符。这种情况通常是由于以下几个原因造成的:数据库版本过低:数据库版本低于CMS所支持的最低版本。 数据库文件版本不匹配:数据库文件版本…

APIO2016 烟火表演

传送门 给定一棵树,带边权。\(1\) 的代价可以使某边权 \(\pm 1\)。求最小代价使从根到叶子距离都相等。 \(n\le 3\times 10^5,w_e\le 10^9\)。\(f_u(x)\) 表示 \(u\) 的子树内把 \(u\) 到叶子的距离都变成 \(x\) 的最小代价。\(F_u(x)\) 表示 \(u\) 的子树内把 \(fa[u]\) 到叶…

织梦dedecms使用weight排序无效怎么办

织梦CMS (DedeCMS) 中使用 weight 排序无效的问题,通常是因为程序内部的排序逻辑存在问题。根据之前提供的信息,这个问题在DedeCMS 5.7版本中存在,并且可以通过修改底层代码来解决。下面是解决此问题的一般步骤: 解决方法定位代码:首先,找到织梦CMS的 plus 目录下的 list…

算法与数据结构——哈希优化策略与算法选择

哈希优化策略 在算法题中,我们通常通过线性查找替换为哈希查找来降低算法的时间复杂度。我们借助一个算法题来加深理解。Question 给定一个整数数组nums和一个目标元素target,请在数组中搜索“和”为target的两个元素,并返回他们的数组索引。返回任意一个即可。线性查找: 以…

织梦DEDECMS怎么实现全站动态浏览

要实现DedeCMS(织梦CMS)全站动态浏览,可以通过以下步骤来进行配置:首页动态化:登录织梦CMS的后台管理系统。 导航到“核心”->“全局配置”。 在“站点设置”标签页中,找到“主页网址”设置,确保主页网址是动态的,例如 http://www.example.com/ 而不是静态的 http:/…