MybatisPlus

一.快速入门

1.引入MybatisPlus的起步依赖

mybatisPlus官方提供了starter。其中集成了Mybatis和MybatisPlus的所有功能,并且实现了自动装配效果。

因此我们可以用MybatisPlus的starter代替Mybatis的starter:

XML 复制代码
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
2.定义Mapper

自定义的Mapper继承MybatisPlus提供的BaseMapper接口:

java 复制代码
public interface UserMapper extends BaseMapper<User>{

}
3.常见注解

MybatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息。

  • 类名驼峰转下划线作为表名

  • 名为id的字段作为主键

  • 变量名驼峰转下划线作为表的字段名

自定义配置:

如果不符合MybatisPlus的约定就要使用自定义配置。

  • @TableName:用来指定表名

    idType枚举(不指定类型并且不给id赋值默认是ASSIGN_ID):

    • AUTO:数据库自增长

    • INPUT:通过set方法自行输入

    • ASSIGN_ID:分配id

  • @TableId:用来指定表中的主键字段信息

  • @TableField :用来指定表中的普通字段信息

使用@TableField的常见场景:

  • 成员变量名与数据库字段名不一致

  • 成员变量名以is开头,且是布尔值(经过反射处理,它会将is去掉作为数据库字段名)

  • 成员变量名与数据库关键字冲突

  • 成员变量名不是数据库字段(数据库中不存在该字段)

4.常用配置
XML 复制代码
mybatis-plus:
  type-aliases-package: com.gty.mp.domain.po #别名扫描包
  mapper-locations: "classpath*:/mapper/**/*.xml" #mapper.xml文件地址, 默认值
  configuration:
    map-underscore-to-camel-case: true #是否开启下划线和驼峰的映射
    cache-enabled: true #是否开启二级映射
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启mybatis打印日志
  global-config:
    db-config:
      id-type: assign_id #id为雪花算法生成,注解配置大于全局配置
      update-strategy: not_null # 更新策略:只更新非空字段

二.核心功能

5.条件构造器
  • QueryWrapperLambdaQueryWrapper通常用来构建select、delete、update的where条件部分

  • UpdateWrapperLambdaUpdateWrapper通常只有在set语句比较特殊才使用

  • 尽量使用LambdaQueryWrapper和LambdaUpdateWrapper,避免硬编码

6.自定义sql

我们可以利用MyBatisPlus的Wrapper来构建复杂的where条件,然后自己定义SQL语句中剩下的部分。

例如:将id为1,2,4的用户余额减200

7.IService接口
(1) IService接口使用流程:
  • 自定义Service接口继承Service接口
  • 自定义Service实现类,实现自定义接口并继承Servicelmpl类
(2) Lambda

IService中还提供了Lambda功能来简化我们的复杂查询及更新功能。

**例一:**根据一些复杂条件用lambda实现对用户的查询,查询条件如下:

a. name:用户名关键字,可以为空

b. status:用户状态,可以为空

c. minBalance:最小余额,可以为空

d. maxBalance:最大余额,可以为空

首先定义一个User的查询条件实体类,UserQuery实体,代码如下:

java 复制代码
package com.gty.mp.model.query;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery{
    @ApiModelProperty("用户名关键字")
    private String name;
    @ApiModelProperty("用户状态:1-正常,2-冻结")
    private Integer status;
    @ApiModelProperty("余额最小值")
    private Integer minBalance;
    @ApiModelProperty("余额最大值")
    private Integer maxBalance;
}

接下来在UserController中定义一个controller方法:

java 复制代码
@ApiOperation("根据复杂条件查询用户接口")
@GetMapping("/list")
public List<UserVO> queryUsers(UserQuery userQuery){
    // 1.查询用户(PO)
    List<User> users = userService.queryUsers(userQuery);
    // 2.把po拷贝到vo
     return BeanUtil.copyToList(users, UserVO.class);
}

在组织查询条件的时候,加入了 username != null 这样的参数,意思就是当条件成立时才会添加这个查询条件,类似Mybatis的mapper.xml文件中的<if>标签。这样就实现了动态查询条件效果了。但是,上述操作过于复杂,因此IService中对LambdaQueryWrapperLambdaUpdateWrapper的用法进一步做了简化。我们无需自己通过new的方式来创建Wrapper,而是直接调用lambdaQuerylambdaUpdate方法:

Lambda查询:

java 复制代码
@Override
public List<User> queryUsers(UserQuery userQuery) {
        return lambdaQuery()
                .like(userQuery.getName() != null, User::getUsername, userQuery.getName())
                .eq(userQuery.getStatus() != null, User::getStatus, userQuery.getStatus())
                .ge(userQuery.getMinBalance() != null, User::getBalance, userQuery.getMinBalance())
                .le(userQuery.getMaxBalance() != null, User::getBalance, userQuery.getMaxBalance())
                .list();
}

lambdaQuery方法中除了可以构建条件,还需要在链式编程的最后添加一个list(),这是在告诉MP我们的调用结果需要是一个list集合。这里不仅可以用list(),可选的方法有:

  • .one():最多1个结果

  • .list():返回集合结果

  • .count():返回计数结果

**例二:**根据id扣减用户余额,余额为0冻结用户

在扣减用户余额时,需要对用户剩余余额做出判断,如果发现剩余余额为0,则应该将status修改为2,这就是说update语句的set部分是动态的。

实现如下:

先在UserController中定义一个Controller方法:

java 复制代码
@ApiOperation("根据id扣减用户余额,余额为0冻结用户接口")
@PutMapping("/{id}/deduction/{money}/lambda")
public void deductionBalance(
    @ApiParam("用户id") @PathVariable("id") Long id,
    @ApiParam("扣减的金额") @PathVariable("money") Integer money){
    userService.deductionBalance(id,money);
}

接下来实现Lambda修改:

java 复制代码
    @Override
    public void deductionBalance(Long id, Integer money) {
        // 1.查询用户
        User user = getById(id);
        // 2.校验用户状态
        if (user == null || user.getStatus() == UserStatus.FREEZE) {
            throw new RuntimeException("用户状态异常!");
        }
        // 3.校验余额是否充足
        if (user.getBalance() < money) {
            throw new RuntimeException("用户余额不足");
        }
        // 4.扣减余额
        int remainBalance = user.getBalance() - money;
        lambdaUpdate()
                .set(User::getBalance, remainBalance)
                .set(remainBalance == 0, User::getStatus, UserStatus.FREEZE)
                .eq(User::getId, id)
                .eq(User::getBalance, user.getBalance()) //乐观锁
                .update();
    }
(3) 批量新增

首先测试逐条插入数据:

java 复制代码
    private User buildUser(int i) {
        User user = new User();
        user.setUsername("user_" + i);
        user.setPassword("123");
        user.setPhone("" + (18688190000L + i));
        user.setBalance(2000);
        user.setInfo(UserInfo.of(24, "英文老师", "female"));
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(user.getCreateTime());
        return user;
    }

    /*
        逐条插入
     */
    @Test
    void testSaveOneByOne() {
        long b = System.currentTimeMillis();
        for (int i = 1; i <= 100000; i++) {
            userService.save(buildUser(i));
        }
        long e = System.currentTimeMillis();
        System.out.println("耗时:" + (e - b));
    }

执行速度十分之慢!!!(因为太慢就不做演示了)

然后是mybatisPlus的批处理:

java 复制代码
    @Test
    void testSaveBatch() {
        // 准备10万条数据
        List<User> list = new ArrayList<>(1000);
        long b = System.currentTimeMillis();
        for (int i = 1; i <= 100000; i++) {
            list.add(buildUser(i));
            // 每1000条批量插入一次
            if (i % 1000 == 0) {
                userService.saveBatch(list);
                list.clear();
            }
        }
        long e = System.currentTimeMillis();
        System.out.println("耗时:" + (e - b));
    }

可以看到使用了批处理以后,比逐条新增效率提高了10倍左右,性能还是不错的。但是会发现,速度并不是很快。根据mybatisPlus源码显示,MybatisPlus的批处理是基于PrepareStatement的预编译模式,然后批量提交,最终在数据库执行时还是会有多条insert语句,逐条插入数据。而想得到最佳性能,最好是将多条SQL合并为一条。

做法如下:

MySQL的客户端连接参数中有这样的一个参数:rewriteBatchedStatements。顾名思义,就是重写批处理的statement语句。这个参数的默认值是false,我们需要修改连接参数,将其配置为true。

在配置文件中,在数据库连接配置中在url后面加上 rewriteBatchedStatements=true 的一个参数:

java 复制代码
url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true

ClientPreparedStatementexecuteBatchInternal中,有判断rewriteBatchedStatements值是否为true并重写SQL的功能,这样效率就有了明显的提升。

三.扩展功能

1.代码生成器
(1)安装插件
(2) 使用

配置数据库信息

dbUrl的填写直接复制application中的数据库配置中的url即可:

2.静态工具

有的时候Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具类:Db,其中的一些静态方法与IService中方法签名基本一致,也可以帮助我们实现CRUD功能:

根据id查询用户和地址,测试如下:

首先在UserController中定义一个controller方法:

java 复制代码
    @ApiOperation("根据id查询用户和地址接口")
    @GetMapping("/{id}/address")
    public UserVO queryUserAndAddressById(@ApiParam("用户id") @PathVariable("id") Long id){
        return userService.queryUserAndAddressById(id);
    }

DB处理:

java 复制代码
    @Override
    public UserVO queryUserAndAddressById(Long id) {
        // 1.查询用户
        User user = getById(id);
        if (user == null || user.getStatus() == UserStatus.FREEZE) {
            throw new RuntimeException("用户状态异常!");
        }
        // 2.查询地址
        List<Address> addresses = Db.lambdaQuery(Address.class).eq(Address::getUserId, user.getId()).list();
        // 3.封装VO
        // 3.1转User的PO为VO
        UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
        // 3.2转地址VO
        if (CollUtil.isNotEmpty(addresses)) {
            userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));
        }
        return userVO;
    }
3.逻辑删除

逻辑删除就是基于代码逻辑模拟删除效果,但并不会真正删除数据。对于一些比较重要的数据,我们往往会采用逻辑删除的方案,即:

  • 在表中添加一个字段标记数据是否被删除

  • 当删除数据时把标记置为true

  • 查询时过滤掉标记为true的数据

一旦采用了逻辑删除,所有的查询和删除逻辑都要跟着变化,非常麻烦。

为了解决这个问题,MybatisPlus就添加了对逻辑删除的支持,无需改变方法调用的方式,而是在底层帮我们自动CRUD的语句。我们要做的就是在application.yaml文件中配置逻辑删除的字段名称和值即可。

注意,只有MybatisPlus生成的SQL语句才支持自动的逻辑删除,自定义SQL需要自己手动处理逻辑删除。

XML 复制代码
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted # 全局逻辑删除的实体字段名
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

注意 ,逻辑删除本身也有自己的问题,比如:

  • 会导致数据库表垃圾数据越来越多,影响查询效率

  • SQL中全都需要对逻辑删除字段做判断,影响查询效率。因此,我不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表的办法。

4.枚举处理器

①给枚举中的与数据库对应value值添加@EnumValue注解

③然后把User类中的status字段改为UserStatus 类型

④同时,为了使页面查询结果也是枚举格式,我们需要修改UserVO中的status属性

⑤在配置文件中配置统一的枚举处理器,实现类型转换

5.JSON处理器

①定义一个单独实体类来与info字段的属性匹配

代码如下:

java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor(staticName = "of")
public class UserInfo {
    private Integer age;
    private String intro;
    private String gender;
}

②接下来,将User类的info字段修改为UserInfo类型,并声明类型处理器:

java 复制代码
@Data
@TableName(value = "user",autoResultMap = true)
public class User {
    private Long id;
    private String username;
    private String password;
    private String phone;
    @TableField(typeHandler = JacksonTypeHandler.class)
    private UserInfo info;
    private UserStatus status;
    private Integer balance;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

③同时,为了让页面返回的结果也以对象格式返回,我们要修改UserVO 中的info字段

四.插件功能

1.分页插件
(1)配置分页插件

代码如下:

java 复制代码
package com.gty.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;

/**
 * @author Gyu
 * date 2024/10/18 17:56
 * 功能描述:
 */
@Configuration
public class MybatisConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        // 初始化核心插件
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        paginationInnerInterceptor.setMaxLimit(1000L);
        // 添加分页插件
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        return interceptor;
    }
}
(2)分页API

编写分页查询测试:

java 复制代码
    @Test
    void testPageQuery() {
        // 1.准备分页条件
        // 1.1分页条件
        int pageNo = 1; //页码
        int pageSize = 2; //每页大小
        Page<User> page = Page.of(pageNo, pageSize);
        // 1.2排序条件
        page.addOrder(new OrderItem("balance",true)); //以用户余额进行升序排序
        page.addOrder(new OrderItem("id",true)); //余额相等则用用户id进行升序排序

        // 2.分页查询
        userService.page(page);

        // 3.解析
        long total = page.getTotal(); //总条数
        System.out.println("total = " + total);
        long pages = page.getPages(); //总页数
        System.out.println("pages = " + pages);
        List<User> users = page.getRecords(); //第一页记录数
        users.forEach(System.out::println);
    }

运行结果如下:

这里用到了分页参数,Page,即可以支持分页参数,也可以支持排序参数。常见的API如下 :

2.通用分页实体
(1)实体

UserQuery之前已经定义过了,并且其中已经包含了过滤条件,其中缺少的仅仅是分页条件,而分页条件不仅仅用户分页查询需要,以后其它业务也都有分页查询的需求。因此建议将分页查询条件单独定义为一个PageQuery实体:

PageQuery是前端提交的查询参数,一般包含四个属性:

  • pageNo:页码

  • pageSize:每页数据条数

  • sortBy:排序字段

  • isAsc:是否升序

代码如下:

java 复制代码
@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {
    @ApiModelProperty("页码")
    private Long pageNo = 1L;
    @ApiModelProperty("每一页的条数")
    private Long pageSize = 5L;
    @ApiModelProperty("排序字段")
    private String sortBy;
    @ApiModelProperty("是否升序")
    private Boolean isAsc = true;
}

然后,让UserQuery继承这个实体:

java 复制代码
package com.gty.mp.model.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:

代码如下:

java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("分页结果")
public class PageDTO<V> {
    @ApiModelProperty("总条数")
    private Long total;
    @ApiModelProperty("总页数")
    private Long pages;
    @ApiModelProperty("集合")
    private List<V> list;
}
(2)开发接口

UserController中定义分页查询用户的接口:

java 复制代码
@ApiOperation("User的分页查询接口")
@GetMapping("/page")
public PageDTO<UserVO> queryUsersPage(UserQuery query){
return userService.queryUsersPage(query);
}

然后在IUserService中创建queryUsersPage方法:

java 复制代码
 PageDTO<UserVO> queryUsersPage(UserQuery query);

接下来,在UserServiceImpl中实现该方法:

java 复制代码
    @Override
    public PageDTO<UserVO> queryUsersPage(UserQuery query) {
        // 1.构建条件
        // 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(query.getName() != null, User::getUsername, query.getName())
                .eq(query.getStatus() != null, User::getStatus, query.getStatus())
                .page(page);

        // 3.封装VO结果
        PageDTO<UserVO> userPageDTO = new PageDTO<>();
        // 3.1总条数
        userPageDTO.setTotal(p.getTotal());
        // 3.2总页数
        userPageDTO.setPages(p.getPages());
        // 3.3当前页数据
        if(CollUtil.isEmpty(p.getRecords())){
            userPageDTO.setList(Collections.emptyList());
            return userPageDTO;
        }
        // 3.4拷贝user的vo
        userPageDTO.setList(BeanUtil.copyToList(p.getRecords(), UserVO.class));
        return userPageDTO;
    }
(3) 封装通用分页功能
  • 在上述代码中,从PageQueryMybatisPlusPage之间转换的过程还是比较麻烦的。 我们可以在PageQuery这个实体中定义一个工具方法,简化开发。

    改造PageQuery类,可以定义一些工具方法,这样会简化在一些繁琐代码:

java 复制代码
package com.gty.mp.model.query;

import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @author Gyu
 * date 2024/10/18 18:15
 * 功能描述:
 */

@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {
    @ApiModelProperty("页码")
    private Long pageNo = 1L;
    @ApiModelProperty("每一页的条数")
    private Long pageSize = 5L;
    @ApiModelProperty("排序字段")
    private String sortBy;
    @ApiModelProperty("是否升序")
    private Boolean isAsc = true;

    /**
     * 将查询到的对象转换为分页格式
     *
     * @param orders 传入OrderItem对象
     * @param <T>    支持泛型
     * @return 返回page对象
     */
    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;
    }

    /**
     * 将查询到的对象转换为分页格式,不需要传入new OrderItem对象,只需要排序字段和排序方式
     *
     * @param defaultSortBy 排序字段
     * @param isAsc         排序方式
     * @param <T>           支持泛型
     * @return 返回page对象
     */
    public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc) {
        return this.toMpPage(new OrderItem(defaultSortBy, isAsc));
    }

    /**
     * 将查询到的对象转换为分页格式,用create_time字段进行降序排序
     *
     * @param <T> 支持泛型
     * @return 返回page对象
     */
    public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {
        return toMpPage("create_time", false);
    }

    /**
     * 将查询到的对象转换为分页格式,用update_time字段进行降序排序
     *
     * @param <T> 支持泛型
     * @return 返回page对象
     */
    public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() {
        return toMpPage("update_time", false);
    }

    /**
     * 将查询到的对象转换为分页格式,用id字段进行升序排序
     *
     * @param <T> 支持泛型
     * @return 返回page对象
     */
    public <T> Page<T> toMpPageDefaultSortByIdAsc() {
        return toMpPage("id", true);
    }
}

这样我们就可以省去在开发对从PageQueryPage的的转换:

java 复制代码
// 1.构建条件
/*      // 1.1.分页条件
        Page<User> page = Page.of(query.getPageNo(), query.getPageSize());
          // 1.2排序条件(StrUtil.isNotBlank(): 检查字符串是否非空且非空白)
        if(StrUtil.isNotBlank(query.getSortBy())){
            page.addOrder(new OrderItem(query.getSortBy(),query.getIsAsc()));
        }else{
            // 为空默认按照更新时间排序
            page.addOrder(new OrderItem("update_time",false));
        }*/
Page<User> page = query.toMpPage(new OrderItem("update_time", true));
  • 在查询出分页结果后,数据的非空校验,数据的vo转换都是模板代码,编写起来很是麻烦。我们也可以将其封装到PageDTO的工具方法中,简化整个过程。

改造PageDTO类:

java 复制代码
package com.gty.mp.model.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;

/**
 * @author Gyu
 * date 2024/10/18 18:18
 * 功能描述:
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("分页结果")
public class PageDTO<V> {
    @ApiModelProperty("总条数")
    private Long total;
    @ApiModelProperty("总页数")
    private Long pages;
    @ApiModelProperty("集合")
    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);
    }
}

这样也就省去了数据判断和转换:

java 复制代码
// 3.封装VO结果
/*        PageDTO<UserVO> userPageDTO = new PageDTO<>();
        // 3.1总条数
        userPageDTO.setTotal(p.getTotal());
        // 3.2总页数
        userPageDTO.setPages(p.getPages());
        // 3.3当前页数据
        if(CollUtil.isEmpty(p.getRecords())){
            userPageDTO.setList(Collections.emptyList());
            return userPageDTO;
        }
        // 3.4拷贝user的vo
        userPageDTO.setList(BeanUtil.copyToList(p.getRecords(), UserVO.class));*/
return PageDTO.of(p, UserVO.class);

最终代码简化为:

java 复制代码
@Override
public PageDTO<UserVO> queryUsersPage(UserQuery query) {
    // 1.构建条件
    Page<User> page = query.toMpPage(new OrderItem("update_time", true));

    // 2.分页查询
    Page<User> p = lambdaQuery()
        .like(query.getName() != null, User::getUsername, query.getName())
        .eq(query.getStatus() != null, User::getStatus, query.getStatus())
        .page(page);

    // 3.封装VO结果
    return PageDTO.of(p, UserVO.class);
}
相关推荐
Str_Null4 分钟前
Seatunnel运行时报错Caused by: java.lang.NoClassDefFoundError: com/mysql/cj/MysqlType
java·seatunnel
麻花201317 分钟前
WPF里面的C1FlexGrid表格控件添加RadioButton单选
java·服务器·前端
理想不理想v31 分钟前
【经典】webpack和vite的区别?
java·前端·javascript·vue.js·面试
请叫我青哥34 分钟前
第五十二条:谨慎使用重载
java·spring
疯一样的码农1 小时前
Apache Maven简介
java·maven·apache
小安同学iter1 小时前
Java进阶五 -IO流
java·开发语言·intellij-idea
尽兴-1 小时前
Redis模拟延时队列 实现日程提醒
java·redis·java-rocketmq·mq
书埋不住我2 小时前
java第三章
java·开发语言·servlet
boy快快长大2 小时前
将大模型生成数据存入Excel,并用增量的方式存入Excel
java·数据库·excel
孟秋与你2 小时前
【spring】spring单例模式与锁对象作用域的分析
java·spring·单例模式