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);
}
相关推荐
Jabes.yang33 分钟前
Java求职面试: 互联网医疗场景中的缓存技术与监控运维应用
java·redis·spring security·grafana·prometheus·oauth2·互联网医疗
初级炼丹师(爱说实话版)1 小时前
内存泄漏与内存溢出
java
CryptoRzz1 小时前
越南k线历史数据、IPO新股股票数据接口文档
java·数据库·后端·python·区块链
!if1 小时前
springboot mybatisplus 配置SQL日志,但是没有日志输出
spring boot·sql·mybatis
学Java的bb1 小时前
MybatisPlus
java·开发语言·数据库
讓丄帝愛伱1 小时前
Mybatis Log Free插件使用
java·开发语言·mybatis
重生之我要当java大帝1 小时前
java微服务-尚医通-编写医院设置接口上
java·数据库·微服务
夫唯不争,故无尤也1 小时前
Tomcat 内嵌启动时找不到 Web 应用的路径
java·前端·tomcat
心之伊始1 小时前
Netty线程模型与Tomcat线程模型对比分析
java·开发语言
gaoshan123456789101 小时前
‌MyBatis-Plus 的 LambdaQueryWrapper 可以实现 OR 条件查询‌
java·tomcat·mybatis