Java Web 开发:项目通用技能

1922 字
10 分钟
Java Web 开发:项目通用技能

分页插件PageHelper#

我们在使用MyBatis开发过程中会遇到查询过程需要使用分页的场景

比如

/admin/user/list?page=1&limit=20&sort=add_time&order=desc

/admin/goods/list?page=3&limit=20&sort=add_time&order=desc

上面的两个请求中都提供了两个参数,page和limit,MySQL中可以使用limit语句来执行分页相关的查询,这时候需要我们来做一件事就是来计算offset

offset = (page-1)*limit

这时候我们查询goods中的记录的SQL就变为了

SELECT * FROM `market_goods` limit 40,20 -- 其中40为偏移量

这时候其实我们可以使用分页插件来帮我们做分页

配置分页插件#

首先引入依赖

<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.2</version>
</dependency>

然后在MyBatis的mybatis-config.xml配置文件中增加配置

<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--方言:mysql-->
<property name="helperDialect" value="mysql"/>
<!--页码的合理性修正-->
<property name="reasonable" value="true"/>
</plugin>
</plugins>

使用分页#

分页插件的原理是在构建预编译的SQL语句过程中帮我们拼接分页相关的语句,并且给对应的占位符提供值,比如我们定义Mapper接口和映射文件中的标签如下

public interface MarketGoodsMapper {
List<MarketGoods> selectAll();
}
<sql id="Base_Column_List">
<!--@mbg.generated-->
id, goods_sn, `name`, category_id, brand_id, gallery, keywords, brief, is_on_sale,
sort_order, pic_url, share_url, is_new, is_hot, unit, counter_price, retail_price,
detail, add_time, update_time, deleted
</sql>
<select id="selectAll" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from market_goods
</select>

这时候我们去执行查询,做的是查询数据库market_goods表中的所有记录

@Test
public void test1() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
MarketGoodsMapper goodsMapper = sqlSession.getMapper(MarketGoodsMapper.class);
List<MarketGoods> marketGoods = goodsMapper.selectAll();
}

对应的控制台的预编译SQL语句以及提供的参数如下

Terminal window
==> Preparing: select id, goods_sn, `name`, category_id, brand_id, gallery, keywords, brief, is_on_sale, sort_order, pic_url, share_url, is_new, is_hot, unit, counter_price, retail_price, detail, add_time, update_time, deleted from market_goods
==> Parameters:

这里呢并没有使用分页

接下来我们来使用一下分页,在执行查询之前开启分页,传入页码和对应的限制量

PageHelper.startPage(page,limit);

对应代码如下

@Test
public void test2() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
MarketGoodsMapper goodsMapper = sqlSession.getMapper(MarketGoodsMapper.class);
PageHelper.startPage(3, 20);
List<MarketGoods> marketGoods = goodsMapper.selectAll();
}

相较于前面的单元测试,增加了PageHelper.startPage(3, 20);

控制台日志输出的预编译的SQL和给预编译的SQL语句提供的参数如下

Terminal window
==> Preparing: select id, goods_sn, `name`, category_id, brand_id, gallery, keywords, brief, is_on_sale, sort_order, pic_url, share_url, is_new, is_hot, unit, counter_price, retail_price, detail, add_time, update_time, deleted from market_goods LIMIT ?, ?
==> Parameters: 40(Long), 20(Integer)

相较于使用之前增加了LIMIT ?, ?

分别提供的参数40(Long), 20(Integer)

也就是帮我们拼接了分页的语句,并且帮我们添加了offset和limit

进一步获得分页信息#

我们前面在分页的时候还获得了这样的一些信息

@Data
public class PageData {
private Integer limit; // 数据量的限制
private List<?> list; // 数据列表
private Integer page; // 当前页的页码
private Integer pages; // 总的页码
private Integer total; // 总的数据量
}

通过分页的话也可以获得这样的一些信息

将查询的原始结果放入到PageInfo的构造方法中,可以获得一个PageInfo对象,里面封装对应的分页信息

PageInfo pageInfo = new PageInfo<>(marketGoods);

对应的代码如下

@Test
public void test3() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
MarketGoodsMapper goodsMapper = sqlSession.getMapper(MarketGoodsMapper.class);
PageHelper.startPage(3, 20);
List<MarketGoods> marketGoods = goodsMapper.selectAll();
PageInfo pageInfo = new PageInfo<>(marketGoods);
}

对应的PageInfo信息如下

image-20240523214001503

也就是我们所需要的信息在PageInfo中都有,那么我们就可以做一个封装,将PageInfo中的信息封装给PageData

@Test
public void test4() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
MarketGoodsMapper goodsMapper = sqlSession.getMapper(MarketGoodsMapper.class);
PageHelper.startPage(3, 20);
List<MarketGoods> marketGoods = goodsMapper.selectAll();
PageInfo pageInfo = new PageInfo<>(marketGoods);
PageData pageData = new PageData();
pageData.setList(pageInfo.getList());
pageData.setLimit(pageInfo.getPageSize());
pageData.setPage(pageInfo.getPageNum());
pageData.setPages(pageInfo.getPages());
pageData.setTotal((int) pageInfo.getTotal());
}

最后呢还可以在PageData中额外增加方法做PageInfo中的信息的封装

@Data
public class PageData {
private Integer limit; // 数据量的限制
private List<?> list; // 数据列表
private Integer page; // 当前页的页码
private Integer pages; // 总的页码
private Integer total; // 总的数据量
public static PageData list(List list) {
PageInfo pageInfo = new PageInfo<>(list);
PageData pageData = new PageData();
pageData.setList(pageInfo.getList());
pageData.setLimit(pageInfo.getPageSize());
pageData.setPage(pageInfo.getPageNum());
pageData.setPages(pageInfo.getPages());
pageData.setTotal((int) pageInfo.getTotal());
return pageData;
}
}

如果想要获得PageData,这样子调用就可以了

@Test
public void test5() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
MarketGoodsMapper goodsMapper = sqlSession.getMapper(MarketGoodsMapper.class);
PageHelper.startPage(3, 20);
List<MarketGoods> marketGoods = goodsMapper.selectAll();
PageData pageData = PageData.list(marketGoods);
}

Example#

Example类是MyBatisGenerator(逆向工程)生成的类,并且可以帮我们在Mapper接口中提供对应的方法,可以帮我们构造条件

生成的example中包含这几个成员变量

public class MarketGoodsExample {
protected String orderByClause; // 用来做排序的
protected boolean distinct; // 用来做去重的
protected List<Criteria> oredCriteria; // 用来拼接条件的
}

Mapper接口中有selectByExample方法会传入example对象,可以帮我们做查询,并且可以使用distinct去重和排序

List<MarketGoods> selectByExample(MarketGoodsExample example);

对应的映射文件如下

<select id="selectByExample" parameterType="com.cskaoyan.model.MarketGoodsExample" resultMap="BaseResultMap">
<!--@mbg.generated-->
select
<if test="distinct">
distinct
</if>
<include refid="Base_Column_List" />
from market_goods
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
<if test="orderByClause != null">
order by ${orderByClause}
</if>
</select>

distinct和orderByClause#

我们先来使用一下distinctorderByClause,先不构造这两个值来看一下预编译的SQL语句

@Test
public void test1() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
MarketGoodsMapper goodsMapper = sqlSession.getMapper(MarketGoodsMapper.class);
// 传入null或new MarketGoodsExample();
List<MarketGoods> marketGoods = goodsMapper.selectByExample(null);
sqlSession.close();
}
Terminal window
==> Preparing: select id, goods_sn, `name`, category_id, brand_id, gallery, keywords, brief, is_on_sale, sort_order, pic_url, share_url, is_new, is_hot, unit, counter_price, retail_price, detail, add_time, update_time, deleted from market_goods

接着呢使用一下distinctorderByClause

@Test
public void test2() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
MarketGoodsMapper goodsMapper = sqlSession.getMapper(MarketGoodsMapper.class);
MarketGoodsExample example = new MarketGoodsExample();
// 构造example的成员变量distinct和orderByClause
example.setDistinct(true);
example.setOrderByClause("add_time desc");
List<MarketGoods> marketGoods = goodsMapper.selectByExample(example);
sqlSession.close();
}
Terminal window
==> Preparing: select distinct id, goods_sn, `name`, category_id, brand_id, gallery, keywords, brief, is_on_sale, sort_order, pic_url, share_url, is_new, is_hot, unit, counter_price, retail_price, detail, add_time, update_time, deleted from market_goods order by add_time desc

在select和id之间添加了distinct

在最后添加了order by add_time desc

条件#

Example中的另一个成员变量List<Criteria> oredCriteria是用来帮我们拼接条件的,如果需要添加条件则向这个list中添加值

这里是一个Criteria的List

其中Criteria的定义如下,其中的Criteria又是Criterion的List

public static class Criteria extends GeneratedCriteria {
protected Criteria() {
super();
}
}
protected abstract static class GeneratedCriteria {
protected List<Criterion> criteria;
protected GeneratedCriteria() {
super();
criteria = new ArrayList<>();
}
}

这里的Criterion其实是单个条件

我们画一下Example组成图如下

Example组成

比如我们想要构造一个这样的条件

where goods_sn = '1006013' and name like '%羊毛%' and category_id = 1008009

这里其实我们使用其中的一个Criteria就可以了,其中的Criteria就是一个List<Criterion>

goods_sn = ‘1006013’

name like ‘%羊毛%’

category_id = 1008009

这里分别都是一个Criterion

我们绝大多数情况构造的也是一个Criteria就可以了,其中的Criteria就是一个List<Criterion>

我们实现的代码如下

@Test
public void test3() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
MarketGoodsMapper goodsMapper = sqlSession.getMapper(MarketGoodsMapper.class);
MarketGoodsExample example = new MarketGoodsExample();
// 构造example的成员变量distinct和orderByClause
example.setDistinct(true);
example.setOrderByClause("add_time desc");
// 构造一个Criteria也就是一个List<Criterion>
MarketGoodsExample.Criteria criteria = example.createCriteria();
// 构造一个Criterion
criteria.andGoodsSnEqualTo("1006013");
// 构造一个Criterion
criteria.andNameLike("%羊毛%");
// 构造一个Criterion
criteria.andCategoryIdEqualTo(1008009);
List<MarketGoods> marketGoods = goodsMapper.selectByExample(example);
sqlSession.close();
}

对应的Example如下

image-20240523223233050
image-20240523223233050

对应的预编译SQL语句会在这里添加条件

Terminal window
==> Preparing: select distinct id, goods_sn, `name`, category_id, brand_id, gallery, keywords, brief, is_on_sale, sort_order, pic_url, share_url, is_new, is_hot, unit, counter_price, retail_price, detail, add_time, update_time, deleted from market_goods WHERE ( goods_sn = ? and `name` like ? and category_id = ? ) order by add_time desc

WHERE ( goods_sn = ? and name like ? and category_id = ? ) 这部分内容是我们的这几行代码提供的

// 构造一个Criteria也就是一个List<Criterion>
MarketGoodsExample.Criteria criteria = example.createCriteria();
// 构造一个Criterion
criteria.andGoodsSnEqualTo("1006013");
// 构造一个Criterion
criteria.andNameLike("%羊毛%");
// 构造一个Criterion
criteria.andCategoryIdEqualTo(1008009);

我们在后面使用过程中添加对应的条件使用

criteria.and-字段名称-比较逻辑运算符(对应的值)

基本上可以构造出所有的基于单表的条件

其他#

另外创建Criteria也可以使用example的or方法来获取

MarketGoodsExample.Criteria criteria = example.or();

如果要清空example中的内容可以使用其clear方法

example.clear();

注意#

example不要全局共享(线程不安全),每次创建一个新的example对象

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Java Web 开发:项目通用技能
https://firefly-mu-weld.vercel.app/posts/20-项目通用技能/
作者
Daisy
发布于
2026-06-10
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Daisy
Hello, I'm Daisy.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签

文章目录