4.插件功能
MybatisPlus
提供了很多的插件功能,进一步拓展其功能。目前已有的插件有:
PaginationInnerInterceptor
:自动分页TenantLineInnerInterceptor
:多租户DynamicTableNameInnerInterceptor
:动态表名OptimisticLockerInnerInterceptor
:乐观锁IllegalSQLInnerInterceptor
:sql
性能规范BlockAttackInnerInterceptor
:防止全表更新与删除
注意:
使用多个分页插件的时候需要注意插件定义顺序,建议使用顺序如下:
- 多租户,动态表名
- 分页,乐观锁
- sql 性能规范,防止全表更新与删除
这里我们以分页插件为里来学习插件的用法。
4.1.分页插件
在未引入分页插件的情况下,MybatisPlus
是不支持分页功能的,IService
和BaseMapper
中的分页方法都无法正常起效。
所以,我们必须配置分页插件。
4.1.1.配置分页插件
在项目中新建一个配置类:
其代码如下:
package com.itheima.mp.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 {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {// 初始化核心插件MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 添加分页插件PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);paginationInnerInterceptor.setMaxLimit(1000L);interceptor.addInnerInterceptor(paginationInnerInterceptor);return interceptor;}
}
4.1.2.分页API
编写一个分页查询的测试:
@Testvoid testPageQuery(){int pageNo = 1, pageSize = 2;// 1.准备分页条件// 1.1.分页条件Page<User> page = Page.of(pageNo, pageSize);// 1.2.排序条件 (先按照balance 升序,如果存在一致的balance 再按照 id 进行升序排序)page.addOrder(new OrderItem("balance",true));page.addOrder(new OrderItem("id",true));// 2.分页查询Page<User> p = userService.page(page);// 3.解析long total = p.getTotal(); // 总记录数System.out.println("total = "+total);long pages = p.getPages(); // 总页数System.out.println("pages = "+pages);List<User> users = p.getRecords(); // 分页结果users.forEach(System.out::println);}
运行的SQL
如下:
4.2.通用分页实体
现在要实现一个用户分页查询的接口,接口规范如下:
返回值如下:
这里需要定义3个实体:
UserQuery
:分页查询条件的实体,包含分页
、排序参数
、过滤条件
PageDTO
:分页结果实体,包含总条数
、总页数
、当前页数据
UserVO
:用户页面视图实体
4.2.1.实体
由于UserQuery
之前已经定义过了,并且其中已经包含了过滤条件,具体代码如下:
package com.itheima.mp.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
:是否升序
package com.itheima.mp.domain.query;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {@ApiModelProperty("页码")private Long pageNo;@ApiModelProperty("页码")private Long pageSize;@ApiModelProperty("排序字段")private String sortBy;@ApiModelProperty("是否升序")private Boolean isAsc;
}
然后,让我们的UserQuery
继承这个实体:
分页实体PageDTO
:
代码如下:
package com.itheima.mp.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;
}
最后是UserVO
,在之前有写过:
4.2.2.开发接口
在UserController
中定义分页查询用户的接口:
代码如下:
@ApiOperation("根据分页条件查询用户的接口")@GetMapping("/page")public PageDTO<UserVO> queryUsersPage(UserQuery query){return userService.queryUsersPage(query);}
然后在IUserService
中创建queryUsersPage
方法:
PageDTO<UserVO> queryUsersPage(PageQuery query);
接下来,在UserServiceImpl
中实现该方法:
@Overridepublic PageDTO<UserVO> queryUsersPage(UserQuery query) {String name = query.getName();Integer status = query.getStatus();Integer maxBalance = query.getMaxBalance();Integer minBalance = query.getMinBalance();// 1.1.分页条件Page<User> page = Page.of(query.getPageNo(), query.getPageSize());// 1.2.排序条件if (StrUtil.isNotBlank(query.getSortBy())) { // 排序条件不为空page.addOrder(new OrderItem(query.getSortBy(), query.getIsAsc()));} else { // 排序条件为空,默认按照更新时间进行降序排序page.addOrder(new OrderItem("update_time", false));}// 2.分页查询Page<User> p = lambdaQuery().like(name != null, User::getUsername, name).eq(status != null, User::getStatus, status).ge(minBalance != null, User::getBalance, minBalance).le(maxBalance != null, User::getBalance, maxBalance).page(page);// 3.封装VO结果PageDTO<UserVO> dto = new PageDTO<>();// 3.1.总条数dto.setTotal(p.getTotal());// 3.2.总页数dto.setPages(p.getPages());// 3.3.当前页数据List<User> records = p.getRecords();if (CollUtil.isEmpty(records)) {dto.setList(Collections.emptyList());return dto;}// 3.4.拷贝user的vodto.setList(BeanUtil.copyToList(records, UserVO.class));// 4.返回return dto;}
启动项目,在测试效果:
4.2.3.改造PageQuery实体
在刚才的代码中,从PageQuery
到MybatisPlus
的Page
之间转换的过程还是比较麻烦的。
我们完全可以在PageQuery
这个实体中定义一个工具方法,简化开发。
像这样:
package com.itheima.mp.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
到MybatisPlus
的Page
的转换,UserServiceImpl
简化如下:
Page<User> page = query.toMpPageDefaultSortByUpdateTimeDesc();
4.2.4.改造PageDTO实体
在查询出分页结果后,数据的非空校验,数据的vo
转换都是模板代码,编写起来很麻烦。
我们完全可以将其封装到PageDTO
的工具方法中,简化整个过程:
代码如下:
package com.itheima.mp.domain.dto;import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
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
@ApiModel(description = "分页结果")
public class PageDTO<T> {@ApiModelProperty("总条数")private Long total;@ApiModelProperty("总页数")private Long pages;@ApiModelProperty("集合")private List<T> 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);}
}
最终业务简化如下:
代码如下:
@Overridepublic PageDTO<UserVO> queryUsersPage(UserQuery query) {String name = query.getName();Integer status = query.getStatus();Integer maxBalance = query.getMaxBalance();Integer minBalance = query.getMinBalance();// 1.构建分页条件Page<User> page = query.toMpPageDefaultSortByUpdateTimeDesc();// 2.分页查询Page<User> p = lambdaQuery().like(name != null, User::getUsername, name).eq(status != null, User::getStatus, status).ge(minBalance != null, User::getBalance, minBalance).le(maxBalance != null, User::getBalance, maxBalance).page(page);// 3.封装返回return PageDTO.of(page, UserVO.class);}
如果是希望自定义PO
到VO
的转换过程,可以这样做:
@Overridepublic PageDTO<UserVO> queryUsersPage(UserQuery query) {String name = query.getName();Integer status = query.getStatus();Integer maxBalance = query.getMaxBalance();Integer minBalance = query.getMinBalance();// 1.构建分页条件Page<User> page = query.toMpPageDefaultSortByUpdateTimeDesc();// 2.分页查询Page<User> p = lambdaQuery().like(name != null, User::getUsername, name).eq(status != null, User::getStatus, status).ge(minBalance != null, User::getBalance, minBalance).le(maxBalance != null, User::getBalance, maxBalance).page(page);// 3.封装返回// return PageDTO.of(page, UserVO.class);return PageDTO.of(page, user -> {// 拷贝属性到VOUserVO vo = BeanUtil.copyProperties(user, UserVO.class);// 用户名脱敏String username = vo.getUsername();vo.setUsername(username.substring(0, username.length() - 2) + "**");return vo;});}
查询的结果如下: