一、前言
无论是自我学习中,还是在工作中,固然会遇到与前端搭配实现分页的功能,发现有几种方式,特此记录一下。
二、实现方式
2.1、分页功能直接交给前端实现
这种情况也是有的,(根据业务场景且仅仅只能用于数据量少的情况)。即后端不做任何数据的限制,直接把全部数据返回给前端,前端通过组件实现分页,筛选等功能。请不要轻视该方式,好处即只需要前后端交互一次。
2.2、数据库SQL的限制条件
即给搜索语句加上条件,限制查询出来的数据个数。(这里不同数据库可能sql语句写法不一样)
- mysql数据库是使用 limit n,m 从第n个开始,往后取m个(注 不包括第n个数据)
- oracle数据库是使用 OFFSET n ROWS FETCH NEXT m ROWS ONLY 从第n行开始,往后取m行(注 不包括第n行数据)
oracle的可以查看这篇文章:oracle中将数据进行排序之后,获取前几行数据的写法(rownum、fetch方式)
2.3、使用List集合的截取功能实现
即将数据都查到内存中List集合,在内存中找到要的数据。当然有人说这种方式还不如第二点,但请具体情况具体分析,有可能需求要的数据,是从数据库中查询不到的,需要将原始数据查到内存加工处理数据之后得到,才能进行分页处理。(同理,该方法,只能根据需求且数据量少的情况)。
2.4、插件PageHelper
使用优秀的插件PageHelper,真的很不错。
如果想详细了解PageHelper插件的,可以访问:如何使用分页插件
2.5、SpringData
SpringData我还没用过,这里就不展开详细说明了,后期如果业务使用到了,会更新到这篇文章。
三、详细介绍
分页功能交给前端实现的这里就不展示了,比较我们标题是后端实现分页功能。
3.1、数据库SQL的限制条件(limit,fetch)
sql语句
mysql写法:
SELECT * FROM user2
LIMIT (#{pageNum} - 1) * #{pageSize}, #{pageSize}
oracle写法:
SELECT * FROM user2
OFFSET (#{pageNum} - 1) * #{pageSize} ROWS FETCH NEXT #{pageSize} ROWS ONLY
Dao层(也可以叫Mapper层)
@Mapper
public interface PageTestDao {
// 查数据
// start:从第几条开始,向后要数据
// pageSize:一页多少条数据
List<UserEntity> getUserInfoByParams(@Param("nameParam") String name,
@Param("start") int start,
@Param("pageSize") int pageSize);
// 返回总条数
int getCountByParams(@Param("nameParam") String name);
}
Mapper.xml
<?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.csdn2.page_test.dao.PageTestDao">
<sql id="nameCondition">
<where>
<if test="nameParam != null and nameParam != ''">
name like CONCAT('%', #{nameParam}, '%')
</if>
</where>
</sql>
<select id="getUserInfoByParams" resultType="com.example.csdn2.page_test.entity.UserEntity">
SELECT * FROM user2
<include refid="nameCondition" />
LIMIT #{start}, #{pageSize}
</select>
<select id="getCountByParams" resultType="int">
SELECT COUNT(*) FROM user2
<include refid="nameCondition" />
</select>
</mapper>
Service实现层
@Service
@RequiredArgsConstructor
public class PageTestService {
private final PageTestDao pageTestDao;
public PageResponse<UserEntity> getPageTest(UserRequest userRequest) {
final List<UserEntity> userEntityList = pageTestDao.getUserInfoByParams(userRequest.getNameParam(),
userRequest.getStart(), userRequest.getPageSize());
final int total = pageTestDao.getCountByParams(userRequest.getNameParam());
return new PageResponse<>(userEntityList, total);
}
}
PageRequest
// 若分页的需求很多,可把分页相关的参数抽出来
@Data
public class PageRequest {
// 第几页
private int pageNum;
// 每页几行数据
private int pageSize;
// 计算从第几行开始
// 无论是limit、还是fetch 都是从某一行数据开始,向后取 pageSize 条数据
public int getStart() {
if (pageNum <= 0) {
return 0;
}
return (pageNum - 1) * pageSize;
}
}
UserRequest
// 入参
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserRequest extends PageRequest {
// 搜索参数
private String nameParam;
}
PageResponse
我这边只返回给前端查询的某页数据、和一共多少条数据,如果前端需要知道可以分多少页,需要前端自己计算一下,当然后端其实也可以计算,只需要添加一个参数和一个方法。
// 返回实体类,因为分页需要返回总条数,前端好做下标第几页
@Data
@AllArgsConstructor
public class PageResponse<T> {
private List<T> data;
// 总条数
private int total;
}
Controller层
@RestController
@RequestMapping("/pageTest")
public class PageTestController {
private final PageTestService pageTestService;
@PostMapping("/page-test")
public PageResponse<UserEntity> getPageTest(@RequestBody UserRequest userRequest){
return pageTestService.getPageTest(userRequest);
}
}
运行结果
3.2、使用List集合的截取功能(subList())实现
先看一下List的截取
// 从第几个下标,到第几个下标
List<E> subList(int fromIndex, int toIndex);
public void test_ListSub() {
// 创建模拟数据,字符串 0-9的集合
final List<String> list = IntStream.range(0, 10)
.mapToObj(i -> i + "")
.collect(Collectors.toList());
System.out.println(list);
// 截取从下标0到5的数据
System.out.println(list.subList(0, 5));
// 截取从下标3到5的数据
System.out.println(list.subList(3, 5));
}
回归上述分页例子,代码改成如下:
dao层 不加 limit 条件
SELECT * FROM user2
name like CONCAT('%', #{nameParam}, '%')
server层
public PageResponse<UserEntity> getPageTestByListSub(UserRequest userRequest) {
final List<UserEntity> allData = pageTestDao.getUserInfoByParamsNoLimit(userRequest.getNameParam());
// 下标开始
final int start = userRequest.getStart();
// 下标结束
final int end = start + userRequest.getPageSize();
// 截取数据
final List<UserEntity> userEntityList = allData.subList(start, end);
final int total = pageTestDao.getCountByParams(userRequest.getNameParam());
return new PageResponse<>(userEntityList, total);
}
3.3、插件PageHelper
这是一个特别好用的分页插件。
其实PageHelper官网中有详细的文档以及例子:https://pagehelper.github.io/docs/howtouse/
下面例子只是讲其与springboot结合的核心内容,即快速开发:
引入相关jar包坐标到pom.xml中
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
配置项目application.yml文件
#bybatis分页插件配置
pagehelper:
helper-dialect: mysql #数据库
reasonable: true
support-methods-arguments: true
params: count=countSql
3.3.1、案例1
前端所需要的数据就是数据库中表的数据
dao层的sql不需要加 Limit 条件(因为PageHelper会自动帮忙加的)
SELECT * FROM user2
name like CONCAT('%', #{nameParam}, '%')
service层修改如下
public PageInfo<UserEntity> getPageTest(UserRequest userRequest) {
// 告诉PageHelper数据要从第几页,每页多少条数据
// 注:一定要在select查询语句之前使用该方法,否则无效
PageHelper.startPage(userRequest.getPageNum(), userRequest.getPageSize());
// 查询sql
final List<UserEntity> userEntityList =
pageTestDao.getUserInfoByParamsNotLimit(userRequest.getNameParam());
// 返回dto,使用插件自带的PageInfo
return new PageInfo<>(userEntityList);
// 上述逻辑还可以简写为:
// return PageHelper.startPage(userRequest.getPageNum(), userRequest.getPageSize())
// .doSelectPageInfo(() ->
pageTestDao.getUserInfoByParamsNotLimit(userRequest.getNameParam()));
}
结果如下(与之前查询结果一致,没问题)
{
"total": 9,
"list": [
{
"name": "4a",
"pwd": "D"
},
{
"name": "5a",
"pwd": "E"
},
{
"name": "6a",
"pwd": "F"
}
],
"pageNum": 2,
"pageSize": 3,
"size": 3,
"startRow": 4,
"endRow": 6,
"pages": 3,
"prePage": 1,
"nextPage": 3,
"isFirstPage": false,
"isLastPage": false,
"hasPreviousPage": true,
"hasNextPage": true,
"navigatePages": 8,
"navigatepageNums": [
1,
2,
3
],
"navigateFirstPage": 1,
"navigateLastPage": 3
}
3.3.2、案例2
前端所需要的数据不只是数据库中表的数据,还有一些需要Java代码逻辑计算得到的数据。那么上面的PageHelper.startPage(userRequest.getPageNum(), userRequest.getPageSize());就失效了。
public PageInfo<UserEntityResp> getPageTest(UserRequest userRequest) {
//分页类的创建
PageInfo<UserEntityResp> res = new PageInfo<>();
// 查询sql
List<UserEntity> userEntityList =
pageTestDao.getUserInfoByParamsNotLimit(userRequest.getNameParam());
//对userEntityList中的数据进行了一些算法操作,改变了原来从数据库中查询到的数据
//或者以什么排序等等操作,最终得到result
List<UserEntityResp> result = ..... ;
int total = result.size();
//注意:这里的start,end是需要通过userRequest.getPageNum(), userRequest.getPageSize()
//计算得到的
Double index = (Double)Math.ceil(total * 1.0 / userRequest.getPageSize());
if (index.intValue()>=userRequest.getPage()){
start = (userRequest.getPage() - 1) * userRequest.getPageSize();
end = Math.min(start + userRequest.getPageSize(), total);
} else{
start = 0;
end = total;
}
List<InterfaceConfirmTimeResp> pageList = result.subList(start, end);
//将分页相关对象的属性设置
res.setList(pageList);
res.setTotal(result.size());
res.setPageNum(userRequest.getPage());
res.setPageSize(userRequest.getPageSize());
res.setPages((int)Math.ceil(result.size()*1.0/userRequest.getPageSize()));
return res;
}
这是通过PageHelp插件中的PageInfo类和List中的subList()方法实现的,其实一般这种情况用的也是比较多的。
3.3.3、为什么PageHelp插件优秀
为什么说该插件很优秀呢,查看PageInfo的返回参数,核心内容:
// 当前页
private int pageNum;
// 每页的数量
private int pageSize;
// 当前页的数量
private int size;
// 总记录数
private long total;
// 总页数
private int pages;
// 结果集
private List<T> list;
// 以下内容都是其自动帮生成的
// 对于前端来说极其友好,前端分页功能的全部参数都包含了
// 前一页的页码
private int prePage;
// 下一页的页码
private int nextPage;
// 是否为第一页
private boolean isFirstPage = false;
// 是否为最后一页
private boolean isLastPage = false;
// 是否有前一页
private boolean hasPreviousPage = false;
// 是否有下一页
private boolean hasNextPage = false;
// 导航条上的第一页的页码
private int navigateFirstPage;
// 导航条上的第一页的页码
private int navigateLastPage;
查看PageHelper执行了什么sql语句
3.3.4、spring结合mybatis整合PageHelper框架
Spring整合:导入pom.xml
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>
配置项目配置文件(我在spring和mybatis整合的配置文件中配置的,如果在mybatis核心配置文件中配置,百度一下)
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 依赖数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 注册加载myBatis映射文件 -->
<property name="mapperLocations">
<array>
<value>classpath*:com/yyz/mapper/*Mapper.xml</value>
</array>
</property>
<!-- PageHelper分页配置 -->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<!--使用下面的方式配置参数,一行配置一个,后面会有所有的参数介绍 -->
<value>
<!--helperDialect属性来指定分页插件使用哪种方言。-->
helperDialect=mysql
<!--分页合理化参数,设置为true时,pageNum<=0时会查询第一页,pageNum>pages(超过总数时),会查询最后一页。-->
reasonable=true
<!--为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值,
可以配置 pageNum,pageSize,count,pageSizeZero,reasonable-->
params=count=countSql
<!--支持通过Mapper接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面 params 配
置的字段中取值,查找到合适的值时就会自动分页。-->
supportMethodsArguments=true
<!--默认值为 false。设置为 true 时,允许在运行时根据多数据源自动识别对应方言的分页-->
autoRuntimeDialect=true
</value>
</property>
</bean>
</array>
</property>
<!-- 给数据库实体起别名 -->
<property name="typeAliasesPackage" value="com.yyz.entity;"/>
</bean>
以上就是Java实现分页的几种方式,希望对你有所帮助,如果有其它方式可以在评论区留言!!!
参考文章:java中实现分页的常见几种方式_java 分页-CSDN博客