引言
分页是Web应用中的"电梯",它帮助用户高效、有序地浏览大量信息,避免信息过载。
分页在Web应用中,就像建房时的楼层分隔,既保证了结构的稳固,也方便了居住者逐层探索,提升了体验。
一、MyBatis分页基础
MyBatis分页基础主要涉及两个核心概念:基本原理和分页参数。
1.1 MyBatis分页的基本原理
markdown
- MyBatis作为一个半自动的ORM(对象关系映射)框架,提供了灵活的SQL映射和数据操作能力。分页是数据库查询中常见的需求,MyBatis通过在SQL查询中添加限制条件(通常是`LIMIT`和`OFFSET`),来实现数据的分页显示。
- 基本原理是将整个数据集分成多个小的部分,每次只加载和显示一部分数据,这样可以减少单次加载的数据量,提高应用的性能和用户体验。
1.2 分页参数的介绍
markdown
- **页码(Page Number)** :表示用户当前查看的是第几页数据。页码通常从1开始计数。
- **每页条数(Page Size)** :表示每一页显示的记录数。这个参数允许用户自定义他们希望在每一页看到的记录数量。
在MyBatis中实现分页,通常需要在Mapper接口中定义一个方法来执行分页查询,并在Mapper XML中编写相应的SQL语句,使用LIMIT
和OFFSET
来控制查询结果的数量和起始位置。
例如:
xml
<select id="selectUsers" resultType="User" useCache="true">
SELECT * FROM users
ORDER BY id
LIMIT #{pageNumber}, #{pageSize}
</select>
<!-- #{offset}和#{pageSize}是分页参数,分别代表从第几条记录开始查询和每页显示多少条记录 -->
二、MyBatis分页的常规操作
MyBatis分页的常规操作包括以下几个步骤:
2.1 Mapper接口定义
- 在Mapper接口中定义分页查询的方法。通常,这些方法会接收分页参数,如当前页码和每页显示的记录数。
java
public interface UserMapper {
// 分页查询方法,接收PageInfo对象作为参数
List<User> pageFind(PageInfo pageInfo);
}
2.2 Mapper XML配置
- 在对应的Mapper XML文件中编写分页查询的SQL语句。使用MyBatis的分页插件(如PageHelper)时,通常不需要修改SQL语句本身,只需在查询方法上添加
@Select
注解或在XML中使用<select>
标签。
xml
<!-- Mapper XML中的分页查询 -->
<select id="pageFind" resultType="User">
SELECT * FROM users WHERE 1=1
<if test="userId != null">AND id = #{userId}</if>
</select>
- 如果不使用分页插件,需要手动在SQL中添加
LIMIT
和OFFSET
子句:
xml
<select id="pageFind" resultType="User">
SELECT * FROM users LIMIT #{offset}, #{pageSize}
</select>
2.3 Service层调用
- 在Service层中调用Mapper接口的分页查询方法,并传入分页参数。通常,这些参数会从前端请求中获取。
java
public class UserService {
private UserMapper userMapper;
public List<User> getUsersWithPagination(int pageNum, int pageSize) {
PageInfo pageInfo = new PageInfo(pageNum, pageSize);
return userMapper.pageFind(pageInfo);
}
}
注意:在Service层中,可能还需要处理分页结果,例如计算总记录数、总页数等,并将其封装到分页信息对象中,以便返回给前端。
2.4 Controller层处理
- 在Controller层接收前端的分页请求,并调用Service层的方法获取分页数据。
java
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping
public PageResult<User> pageFind(@RequestParam int pageNum, @RequestParam int pageSize) {
List<User> users = userService.pageFind(pageNum, pageSize);
// 构造分页结果对象,包含数据列表、总记录数、总页数等
return new PageResult<>(users, pageNum, pageSize);
}
}
三、MyBatis分页插件-PageHelper
3.1 分页插件的作用
-
分页插件的主要作用是自动处理SQL查询的分页逻辑,开发者只需编写普通的查询语句,插件会自动添加必要的分页参数(如
LIMIT
和OFFSET
),从而实现分页功能。 -
它减少了重复编写分页逻辑的工作量,提高了开发效率。
3.2 工作机制
-
分页插件通常作为一个MyBatis的拦截器(Interceptor)实现,它拦截执行的SQL语句,然后根据传入的分页参数,动态地修改SQL语句,添加分页相关的条件。
-
插件在执行查询之前,会先执行一个count查询来获取总记录数,以便计算总页数等信息。
3.3 PageHelper的使用
PageHelper是一个流行的MyBatis分页插件,它通过拦截MyBatis的执行器来实现分页功能。以下是使用PageHelper的步骤:
3.3.1 添加依赖
在项目的pom.xml
文件中添加PageHelper的依赖项。
xml
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>
3.3.2 配置MyBatis
在MyBatis的配置文件mybatis-config.xml
中添加PageHelper插件。
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- myBatis本身的配置,开启下划线驼峰自动转换 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启日志输出 -->
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 4.0.0以后版本可以不设置该参数 -->
<!--<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="true"/>
<!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
<!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
<!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,orderBy,不配置映射的用默认值 -->
<!-- 不理解该含义的前提下,不要随便复制该配置 -->
<!--<property name="params" value="pageNum=start;pageSize=limit;"/>-->
<!-- 支持通过Mapper接口参数来传递分页参数 -->
<property name="supportMethodsArguments" value="true"/>
<!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
<property name="returnPageInfo" value="check"/>
</plugin>
</plugins>
</configuration>
3.3.3 具体使用代码
核心就在于下面的PageHelper.startPage(paramDto.getPageNum(), paramDto.getPageSize());
;在xml代码里面就不再需要手写limit语句。
java
/**
* 分页查询
*
* @param paramDto 参数
* @return 分页数据
*/
@Transactional(propagation = Propagation.REQUIRED, readOnly = true, rollbackFor = Exception.class)
@Override
public MessageResponse pageFind(TrafficDto paramDto) {
/* 分页查询-会将后面第一个select拦截分页 */
PageHelper.startPage(paramDto.getPageNum(), paramDto.getPageSize());
List<TrafficVo> list = trafficCalendarMapper.selectByParam(paramDto);
// 封装结果集
PageInfo<TrafficVo> pageInfo = new PageInfo<>(list);
PageResult<TrafficVo> pageResult = new PageResult<>(pageInfo.getPageNum(), pageInfo.getTotal(), pageInfo.getPages(), list);
return Response.success(pageResult);
}
四、干货:PageHelper分页bug
4.1 bug描述
在使用PageHelper分页插件的时候,如果我们的对象存在一对多的情况,那么分页就会主线条目不对;具体代码如下:
xml
<!-- mybatis collection 分页bug处理案例 一对多 -->
<resultMap id="BaseResultMapVo" type="com.haha.admin.plus.entity.vo.AllowUserListVo">
<id property="id" column="id" jdbcType="VARCHAR"/>
<result property="email" column="email" jdbcType="VARCHAR"/>
<collection property="list" ofType="com.haha.admin.plus.entity.vo.PremiumUserExtendOrderVo">
<result property="extendId" column="extendId" jdbcType="VARCHAR"/>
<result property="orderId" column="orderId" jdbcType="VARCHAR"/>
<result property="orderTotalCost" column="orderTotalCost" jdbcType="DECIMAL"/>
</collection>
</resultMap>
上面的代码查出来的分页数据,一旦list的size大于1,分页数据就会不准确;这个大家可以自己实验,这里主要说说解决办法。
4.2 解决方案
俗话说:毒蛇出没之处,七步之内必有解药;这个插件的bug也是稍微变动一下就OK了,咱一起瞧瞧。
- 第一步:改造上面的resultMap仔细看实际只是多了一个
select="selectExtendOrder" column="extendId"
xml
<!-- mybatis collection 分页bug处理案例 一对多 -->
<resultMap id="BaseResultMapVo" type="com.haha.admin.plus.entity.vo.AllowUserListVo">
<id property="id" column="id" jdbcType="VARCHAR"/>
<result property="email" column="email" jdbcType="VARCHAR"/>
<collection property="list" ofType="com.haha.admin.plus.entity.vo.PremiumUserExtendOrderVo"
select="selectExtendOrder" column="extendId">
<result property="extendId" column="extendId" jdbcType="VARCHAR"/>
<result property="orderId" column="orderId" jdbcType="VARCHAR"/>
<result property="orderTotalCost" column="orderTotalCost" jdbcType="DECIMAL"/>
</collection>
</resultMap>
- 第二步:selectExtendOrder是什么呢?咱直接看下面,这里可以看出这里是一个查询sql
xml
<select id="selectExtendOrder"
resultType="com.haha.admin.plus.entity.vo.PremiumUserExtendOrderVo">
SELECT
eo.extend_id as extendId,
eo.order_id AS orderId,
eo.order_total_cost AS orderTotalCost
FROM
premium_user_extend_order AS eo
WHERE
eo.extend_id = #{extendId}
</select>
- 注意:BaseResultMapVo也是对应的一个查询,如下:
xml
<select id="selectByEmailOrCardNoList" resultMap="BaseResultMapVo">
SELECT
a.id,
a.email,
FROM
allow_user_list AS a
WHERE 1 = 1
<if test="paramDto.email != null and paramDto.email != ''">
AND a.email LIKE CONCAT("%", #{paramDto.email}, "%")
</if>
ORDER BY a.created DESC
</select>
好了,到这里干货就结束了
五、总结
分页是我们做系统必不可少的一个操作,我曾经看到过一个代码,把表全部查到内存里面,然后在内存分页,所导致的结果就是OOM,这种骚操作咱就不要做了
各种ORM框架肯定都是支持真正的分页的,当然里面可能有一些隐藏的坑,咱且行且珍惜。
希望本文对您有所帮助。如果有任何错误或建议,请随时指正和提出。
同时,如果您觉得这篇文章有价值,请考虑点赞和收藏。这将激励我进一步改进和创作更多有用的内容。
感谢您的支持和理解!