文章目录
- 插件功能
- 分页插件
- 配置分页插件
- 分页API
- 通用分页实体
- 实体
- 开发接口
- 改造PageQuery实体
- 改造PageDTO实体
- 仓库地址
插件功能
MybatisPlus提供了很多的插件功能,进一步拓展其功能。目前已有的插件有:
- PaginationInnerInterceptor:自动分页
- TenantLineInnerInterceptor:多租户
- DynamicTableNameInnerInterceptor:动态表名
- OptimisticLockerInnerInterceptor:乐观锁
- IllegalSQLInnerInterceptor:sql 性能规范
- BlockAttackInnerInterceptor:防止全表更新与删除
分页插件
在未引入分页插件的情况下,MybatisPlus是不支持分页功能的,IService和BaseMapper中的分页方法都无法正常起效。
所以,我们必须配置分页插件。
配置分页插件
在项目中新建一个配置类:
其代码如下:
package com.onenewcode.mpdemo.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 初始化核心插件
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
分页API
编写一个分页查询的测试:
@Test
void testPageQuery() {
// 1.分页查询,new Page()的两个参数分别是:页码、每页大小
Page<User> p = userService.page(new Page<>(2, 2));
// 2.总条数
System.out.println("total = " + p.getTotal());
// 3.总页数
System.out.println("pages = " + p.getPages());
// 4.数据
List<User> records = p.getRecords();
records.forEach(System.out::println);
}
运行的SQL如下:
这里用到了分页参数,Page,即可以支持分页参数,也可以支持排序参数。常见的API如下:
int pageNo = 1, pageSize = 5;
// 分页参数
Page<User> page = Page.of(pageNo, pageSize);
// 排序参数, 通过OrderItem来指定
page.addOrder(new OrderItem("balance", false));
userService.page(page);
通用分页实体
现在要实现一个用户分页查询的接口,接口规范如下:
参数 | 说明 |
---|---|
请求方式 | GET |
请求路径 | /users/page |
请求参数 | { “pageNo”: 1, “pageSize”: 5, “sortBy”: “balance”, “isAsc”: false, “name”: “o”, “status”: 1 } |
返回值 | { “total”: 100006, “pages”: 50003, “list”: [ { “id”: 1685100878975279298, “username”: “user_9****”, “info”: { “age”: 24, “intro”: “英文老师”, “gender”: “female” }, “status”: “正常”, “balance”: 2000 } ] } |
特殊说明 | - 如果排序字段为空,默认按照更新时间排序 - 排序字段不为空,则按照排序字段排序 |
这里需要定义3个实体:
- UserQuery:分页查询条件的实体,包含分页、排序参数、过滤条件
- PageDTO:分页结果实体,包含总条数、总页数、当前页数据
- UserVO:用户页面视图实体
实体
由于UserQuery之前已经定义过了,并且其中已经包含了过滤条件,具体代码如下:
package com.onenewcode.mpdemo.domain.query;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery {
@ApiModelProperty("用户名关键字")
private String name;
@ApiModelProperty("用户状态:1-正常,2-冻结")
private Integer status;
@ApiModelProperty("余额最小值")
private Integer minBalance;
@ApiModelProperty("余额最大值")
private Integer maxBalance;
}
其中缺少的仅仅是分页条件,而分页条件不仅仅用户分页查询需要,以后其它业务也都有分页查询的需求。因此建议将分页查询条件单独定义为一个PageQuery实体.
PageQuery是前端提交的查询参数,一般包含四个属性:
- pageNo:页码
- pageSize:每页数据条数
- sortBy:排序字段
- isAsc:是否升序
@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {
@ApiModelProperty("页码")
private Integer pageNo;
@ApiModelProperty("页码")
private Integer pageSize;
@ApiModelProperty("排序字段")
private String sortBy;
@ApiModelProperty("是否升序")
private Boolean isAsc;
}
然后,让我们的UserQuery继承这个实体:
package com.onenewcode.mpdemo.domain.query;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery extends PageQuery {
@ApiModelProperty("用户名关键字")
private String name;
@ApiModelProperty("用户状态:1-正常,2-冻结")
private Integer status;
@ApiModelProperty("余额最小值")
private Integer minBalance;
@ApiModelProperty("余额最大值")
private Integer maxBalance;
}
返回值的用户实体沿用之前定一个UserVO实体:
最后,则是分页实体PageDTO:
代码如下:
package com.onenewcode.mpdemo.domain.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel(description = "分页结果")
public class PageDTO<T> {
@ApiModelProperty("总条数")
private Long total;
@ApiModelProperty("总页数")
private Long pages;
@ApiModelProperty("集合")
private List<T> list;
}
开发接口
我们在UserController中定义分页查询用户的接口:
package com.onenewcode.mpdemo.controller;
import com.onenewcode.mpdemo.domain.dto.PageDTO;
import com.onenewcode.mpdemo.domain.query.PageQuery;
import com.onenewcode.mpdemo.domain.vo.UserVO;
import com.onenewcode.mpdemo.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/page")
public PageDTO<UserVO> queryUsersPage(UserQuery query){
return userService.queryUsersPage(query);
}
// 。。。 略
}
然后在IUserService中创建queryUsersPage方法:
PageDTO<UserVO> queryUsersPage(PageQuery query);
接下来,在UserServiceImpl中实现该方法:
@Override
public PageDTO<UserVO> queryUsersPage(PageQuery query) {
// 1.构建条件
// 1.1.分页条件
Page<User> page = Page.of(query.getPageNo(), query.getPageSize());
// 1.2.排序条件
if (query.getSortBy() != null) {
page.addOrder(new OrderItem(query.getSortBy(), query.getIsAsc()));
}else{
// 默认按照更新时间排序
page.addOrder(new OrderItem("update_time", false));
}
// 2.查询
page(page);
// 3.数据非空校验
List<User> records = page.getRecords();
if (records == null || records.size() <= 0) {
// 无数据,返回空结果
return new PageDTO<>(page.getTotal(), page.getPages(), Collections.emptyList());
}
// 4.有数据,转换
List<UserVO> list = BeanUtil.copyToList(records, UserVO.class);
// 5.封装返回
return new PageDTO<UserVO>(page.getTotal(), page.getPages(), list);
}
改造PageQuery实体
在刚才的代码中,从PageQuery到MybatisPlus的Page之间转换的过程还是比较麻烦的。
我们完全可以在PageQuery这个实体中定义一个工具方法,简化开发。
像这样:
package com.onenewcode.mpdemo.domain.query;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Data;
@Data
public class PageQuery {
private Integer pageNo;
private Integer pageSize;
private String sortBy;
private Boolean isAsc;
public <T> Page<T> toMpPage(OrderItem ... orders){
// 1.分页条件
Page<T> p = Page.of(pageNo, pageSize);
// 2.排序条件
// 2.1.先看前端有没有传排序字段
if (sortBy != null) {
p.addOrder(new OrderItem(sortBy, isAsc));
return p;
}
// 2.2.再看有没有手动指定排序字段
if(orders != null){
p.addOrder(orders);
}
return p;
}
public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc){
return this.toMpPage(new OrderItem(defaultSortBy, isAsc));
}
public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {
return toMpPage("create_time", false);
}
public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() {
return toMpPage("update_time", false);
}
}
这样我们在开发也时就可以省去对从PageQuery到Page的的转换:
// 1.构建条件
Page<User> page = query.toMpPageDefaultSortByCreateTimeDesc();
改造PageDTO实体
在查询出分页结果后,数据的非空校验,数据的vo转换都是模板代码,编写起来很麻烦。
我们完全可以将其封装到PageDTO的工具方法中,简化整个过程:
package com.onenewcode.mpdemo.domain.dto;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageDTO<V> {
private Long total;
private Long pages;
private List<V> list;
/**
* 返回空分页结果
* @param p MybatisPlus的分页结果
* @param <V> 目标VO类型
* @param <P> 原始PO类型
* @return VO的分页对象
*/
public static <V, P> PageDTO<V> empty(Page<P> p){
return new PageDTO<>(p.getTotal(), p.getPages(), Collections.emptyList());
}
/**
* 将MybatisPlus分页结果转为 VO分页结果
* @param p MybatisPlus的分页结果
* @param voClass 目标VO类型的字节码
* @param <V> 目标VO类型
* @param <P> 原始PO类型
* @return VO的分页对象
*/
public static <V, P> PageDTO<V> of(Page<P> p, Class<V> voClass) {
// 1.非空校验
List<P> records = p.getRecords();
if (records == null || records.size() <= 0) {
// 无数据,返回空结果
return empty(p);
}
// 2.数据转换
List<V> vos = BeanUtil.copyToList(records, voClass);
// 3.封装返回
return new PageDTO<>(p.getTotal(), p.getPages(), vos);
}
/**
* 将MybatisPlus分页结果转为 VO分页结果,允许用户自定义PO到VO的转换方式
* @param p MybatisPlus的分页结果
* @param convertor PO到VO的转换函数
* @param <V> 目标VO类型
* @param <P> 原始PO类型
* @return VO的分页对象
*/
public static <V, P> PageDTO<V> of(Page<P> p, Function<P, V> convertor) {
// 1.非空校验
List<P> records = p.getRecords();
if (records == null || records.size() <= 0) {
// 无数据,返回空结果
return empty(p);
}
// 2.数据转换
List<V> vos = records.stream().map(convertor).collect(Collectors.toList());
// 3.封装返回
return new PageDTO<>(p.getTotal(), p.getPages(), vos);
}
}
最终,业务层的代码可以简化为:
@Override
public PageDTO<UserVO> queryUserByPage(PageQuery query) {
// 1.构建条件
Page<User> page = query.toMpPageDefaultSortByCreateTimeDesc();
// 2.查询
page(page);
// 3.封装返回
return PageDTO.of(page, UserVO.class);
}
如果是希望自定义PO到VO的转换过程,可以这样做:
@Override
public PageDTO<UserVO> queryUserByPage(PageQuery query) {
// 1.构建条件
Page<User> page = query.toMpPageDefaultSortByCreateTimeDesc();
// 2.查询
page(page);
// 3.封装返回
return PageDTO.of(page, user -> {
// 拷贝属性到VO
UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
// 用户名脱敏
String username = vo.getUsername();
vo.setUsername(username.substring(0, username.length() - 2) + "**");
return vo;
});
}
仓库地址
https://github.com/onenewcode/mp-demo.git