/**
* 获取通知公告列表
*/
@PreAuthorize("@ss.hasPermi('system:notice:list')")
@GetMapping("/list")
public TableDataInfo list(SysNotice notice)
{
startPage();// 此方法配合前端完成自动分页
List<SysNotice> list = noticeService.selectNoticeList(notice);
return getDataTable(list);
}
先概要说明下但很重要:
后端采用基于mybatis
的轻量级分页插件pageHelper
上图方法的notice参数,我们点进去看,其实并没看到分页相关参数。
那我们进到startPage()里看看,会走到如下:
/**
* 分页工具类
*
* @author ruoyi
*/
public class PageUtils extends PageHelper
{
/**
* 设置请求分页数据
*/
public static void startPage()
{
PageDomain pageDomain = TableSupport.buildPageRequest();
Integer pageNum = pageDomain.getPageNum();
Integer pageSize = pageDomain.getPageSize();
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
Boolean reasonable = pageDomain.getReasonable();
PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
}
/**
* 清理分页的线程变量
*/
public static void clearPage()
{
PageHelper.clearPage();
}
}
看来是在上面取分页参数没错了。再往下:
看来这里是ruoyi vue和前端配合的代码,来获取分页参数。那我们看看前端是怎么传的:
原来是get这里。
额外说明下:
ServletUtils.getParameter
方法获取请求参数,该方法底层调用的是 HttpServletRequest
的 getParameter
方法。getParameter
方法主要用于获取 HTTP 请求中 query string
(即 URL 中的查询参数)和 form data
(表单数据,通常是 POST
请求且 Content-Type
为 application/x-www-form-urlencoded
或 multipart/form-data
)里的参数,并不会获取 body
里以 JSON 等其他格式传递的数据。
上面是ruoyi-vue的分页参数获取方式。然后才调用
PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
我们自己也可以封装一个,比如把分页参数放在body里,如刚才的notice里头。然后从body里取出来。最后传给PageHelper.startPage方法。
分页原理:
这个原理一搜都有,不过既然写到这里,就简单说一下省的小白(比如我自己)到处找哈哈。
下面有参考了别人的 ,我觉得很清晰,引用一下,感谢该作者:
------------------------------------------------------------------------------------------------------Mybatis中分页插件PageHelper的底层实现原理_mybatis pagehelper分页原理是什么-优快云博客
PageHelper 的核心原理是:MyBatis 插件机制(拦截器 Interceptor)+ ThreadLocal + SQL 改写
核心步骤有 4 个:
✅ 1. 利用 MyBatis 的插件机制
MyBatis 允许开发者写拦截器拦截 SQL 执行过程,
PageHelper 就写了一个拦截器,拦截的是 Executor.query(…) 方法。
✅ 2. 用 ThreadLocal 暂存分页信息
当你调用:
PageHelper.startPage(1, 10);
1
PageHelper 就会把这个分页参数(页码、每页条数)存到 ThreadLocal 中,方便后面使用。
ThreadLocal 是一个线程本地变量,不会影响其他线程。
✅ 3. 拦截查询语句并修改 SQL
拦截器在查询语句执行前拿到了原始 SQL,然后从 ThreadLocal 里取出分页参数,把 SQL 改成带 LIMIT 的样子:
比如:
SELECT * FROM user
1
变成:
SELECT * FROM user LIMIT 0, 10
1
注意:它不是直接改你写的 SQL,而是改 MyBatis 构造好的 SQL,再交给数据库执行。
✅ 4. 执行分页查询并封装 Page 对象
执行完 SQL 后,PageHelper 还会生成一个 Page<T> 对象,里面包含:
当前页数据
总记录数(通过 count 查询)
总页数
当前页码等信息
方便你在前端展示。
————————————————
原文链接:https://blog.youkuaiyun.com/qq_61033357/article/details/148063691
------------------------------------------------------------------------------------------------------
上面的最后第四点要注意看下了,也就是返回给前端的时候。
这里的list是经过分页后的,应该是Page类型了
上图的PageInfo 是插件框架里的,如果我们自己要存这些信息并返回给前端,可以自定义一个
包含分页信息和数据的类,把PageInfo里头的信息取出来,也可以继承这个PageInfo后做修改。然后把这些东西返回给前端就可以了。
常见坑点:我就搬一下官网的过来。非常重要。
- 常见坑点1:
selectPostById
莫名其妙的分页。例如下面这段代码 -
startPage(); List<User> list; if(user != null){ list = userService.selectUserList(user); } else { list = new ArrayList<User>(); } Post post = postService.selectPostById(1L); return getDataTable(list);
原因分析:这种情况下由于
user
存在null
的情况,就会导致pageHelper
生产了一个分页参数,但是没有被消费,这个参数就会一直保留在这个线程上。 当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。
上面这个代码,应该写成下面这个样子才能保证安全。 -
List<User> list; if(user != null){ startPage(); list = userService.selectUserList(user); } else { list = new ArrayList<User>(); } Post post = postService.selectPostById(1L); return getDataTable(list);
- 常见坑点2:添加了
startPage
方法。也没有正常分页。例如下面这段代码 -
startPage(); Post post = postService.selectPostById(1L); List<User> list = userService.selectUserList(user); return getDataTable(list);
原因分析:只对该语句以后的第一个查询
(Select)
语句得到的数据进行分页。
上面这个代码,应该写成下面这个样子才能正常分页。 -
Post post = postService.selectPostById(1L); startPage(); List<User> list = userService.selectUserList(user); return getDataTable(list);
提示
项目分页插件默认是
Mysql
语法,如果项目改为其他数据库需修改配置application.yml
文件中的属性helperDialect: 你的数据库
-
注意
只要你可以保证在
PageHelper
方法调用后紧跟MyBatis
查询方法,这就是安全的。因为PageHelper
在finally
代码段中自动清除了ThreadLocal
存储的对象。 如果代码在进入Executor
前发生异常,就会导致线程不可用,这属于人为的Bug
(例如接口方法和XML
中的不匹配,导致找不到MappedStatement
时),这种情况由于线程不可用,也不会导致ThreadLocal
参数被错误的使用。