• 【Springcloud微服务】MybatisPlus下篇


    🔥 本文由 程序喵正在路上 原创,CSDN首发!
    💖 系列专栏:Springcloud微服务
    🌠 首发时间:2024年6月4日
    🦋 欢迎关注🖱点赞👍收藏🌟留言🐾

    扩展功能

    代码生成

    在使用 MybatisPlus 以后,基础的 Mapper、Service、PO 代码相对固定,重复编写也比较麻烦。因此 MybatisPlus 官方提供了代码生成器根据数据库表结构生成 PO、Mapper、Service 等相关代码。只不过代码生成器同样要编码使用,也很麻烦。

    所以这里推荐使用另外一款 MybatisPlus 的插件,它可以基于图形化界面完成 MybatisPlus 的代码生成,非常简单。

    安装插件

    在 Idea 的 plugins 市场中搜索并安装 MyBatisPlus 插件:

    在这里插入图片描述
    然后重启你的 Idea 即可使用。

    使用

    刚好数据库中还有一张 address 表尚未生成对应的实体和 mapper 等基础代码,我们可以利用插件生成一下。

    首先需要配置数据库地址,在 Idea 顶部菜单中,找到 other,选择 Config Database,然后填写一些信息:
    在这里插入图片描述

    然后再次点击 Idea 顶部菜单中的 other,然后选择 Code Generator,填写表单信息:

    在这里插入图片描述
    示例:

    在这里插入图片描述

    在这里插入图片描述

    静态工具

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

    在这里插入图片描述
    下面,我们通过一些案例来学习使用静态工具。

    需求:

    1、改造根据 id 查询用户的接口,查询用户的同时,查询出用户对应的所有地址

    由于现在需要额外返回收货地址,所以我们需要定义一个收货地址的 VO:

    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    @Data
    @ApiModel(description = "收货地址VO")
    public class AddressVO {
    
        @ApiModelProperty("id")
        private Long id;
    
        @ApiModelProperty("用户ID")
        private Long userId;
    
        @ApiModelProperty("省")
        private String province;
    
        @ApiModelProperty("市")
        private String city;
    
        @ApiModelProperty("县/区")
        private String town;
    
        @ApiModelProperty("手机")
        private String mobile;
    
        @ApiModelProperty("详细地址")
        private String street;
    
        @ApiModelProperty("联系人")
        private String contact;
    
        @ApiModelProperty("是否是默认 1默认 0否")
        private Boolean isDefault;
    
        @ApiModelProperty("备注")
        private String notes;
    }
    

    然后在 UserVO 中添加一个属性:

    在这里插入图片描述

    修改 UserController 中根据 id 查询用户的业务接口,新建一个方法:

    /**
     * 根据id查询用户
     *
     * @param userId
     * @return
     */
    @GetMapping("/{id}")
    @ApiOperation("根据id查询用户")
    public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long userId) {
        return userService.queryUserAndAddressById(userId);
    }
    

    IUserService 接口中声明该方法:

    /**
     * 根据id查询用户及其收货地址
     *
     * @param userId
     * @return
     */
    UserVO queryUserAndAddressById(Long userId);
    

    UserServiceImpl 中实现方法:

    /**
     * 根据id查询用户及其收货地址
     *
     * @param userId
     * @return
     */
    public UserVO queryUserAndAddressById(Long userId) {
        // 1.查询用户
        User user = getById(userId);
        if (user == null || user.getStatus() == 2) {
            throw new RuntimeException("用户状态异常!");
        }
        // 2.查询收货地址列表
        List<Address> addresses = Db.lambdaQuery(Address.class).eq(Address::getUserId, userId).list();
        // 3.封装数据
        UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
        // 先判断收货地址列表是否为空
        if (CollUtil.isNotEmpty(addresses)) {
            userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));
        }
        // 4.返回数据
        return userVO;
    }
    

    测试:

    在这里插入图片描述

    2、改造根据 id 批量查询用户的接口,查询用户的同时,查询出用户对应的所有地址

    UserController:

    /**
     * 根据id集合查询用户
     *
     * @param ids
     * @return
     */
    @GetMapping
    @ApiOperation("根据id集合查询用户")
    public List<UserVO> queryUserByIds(@RequestParam("ids") List<Long> ids) {
        return userService.queryUserAndAddressByIds(ids);
    }
    

    UserServiceImpl:

    /**
     * 根据id集合查询用户及其收货地址
     *
     * @param ids
     * @return
     */
    public List<UserVO> queryUserAndAddressByIds(List<Long> ids) {
        // 1.查询用户
        List<User> users = listByIds(ids);
        if (CollUtil.isEmpty(users)) {
            return Collections.emptyList(); // 返回空列表
        }
    
        // 2.查询收货地址
        List<Address> addresses = Db.lambdaQuery(Address.class).in(Address::getUserId, ids).list(); //根据用户id查询收货地址
        List<AddressVO> addressVOList = BeanUtil.copyToList(addresses, AddressVO.class);    //转化地址VO
        //将用户收货地址分组处理,相同用户的放入一个集合中
        Map<Long, List<AddressVO>> addressMap = new HashMap<>(0);
        if (CollUtil.isNotEmpty(addressVOList)) {
            addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
        }
    
        // 3.封装数据
        List<UserVO> list = new ArrayList<>(users.size());
        for (User user : users) {
            UserVO vo = BeanUtil.copyProperties(user, UserVO.class);    // 拷贝用户信息
            vo.setAddresses(addressMap.get(user.getId()));  // 设置用户收货地址
            list.add(vo);
        }
    
        return list;
    }
    

    测试:

    在这里插入图片描述

    逻辑删除

    逻辑删除就是基于代码逻辑模拟删除效果,但并不会真正删除数据库中的数据。思路如下:

    • 在表中添加一个字段标记数据是否被删除
    • 当删除数据时把标记置为 1
    • 查询时只查询标记为 0 的数据

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

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

    在 Address 实体类中已经存在一个字段 deleted 用于逻辑删除,所以我们不用再定义:

    在这里插入图片描述

    mybatis-plus:
      global-config:
        db-config:
          logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
          logic-delete-value: 1		 # 逻辑已删除值(默认为 1, 可以不配置)
          logic-not-delete-value: 0 # 逻辑未删除值(默认为 0, 可以不配置)
    

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

    写一个测试类测试一下:

    package com.itheima.mp.service;
    
    import com.itheima.mp.domain.po.Address;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import java.util.List;
    
    @SpringBootTest
    class IAddressServiceTest {
    
        @Autowired
        private IAddressService addressService;
    
        @Test
        void testDeleteByLogic() {
            // 删除方法与以前没有区别
            addressService.removeById(59L);
        }
    
        @Test
        void testQuery() {
            List<Address> list = addressService.list();
            list.forEach(System.out::println);
        }
    }
    

    先执行第一个测试方法,进行删除,结果如下:

    在这里插入图片描述

    再执行第二个方法查询一下,结果如下:

    在这里插入图片描述

    综上, 开启了逻辑删除功能以后,我们就可以像普通删除一样做 CRUD,基本不用考虑代码逻辑问题。还是非常方便的。

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

    • 会导致数据库表垃圾数据越来越多,影响查询效率
    • SQL 中全都需要对逻辑删除字段做判断,影响查询效率

    因此,还是不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表的办法。

    枚举处理器

    User 类中有一个用户状态字段:

    在这里插入图片描述

    像这种字段我们一般会定义一个枚举,做业务判断的时候就可以直接基于枚举做比较。但是我们数据库采用的是 int 类型,对应的PO也是 Integer。因此业务操作时必须手动把枚举与 Integer 转换,非常麻烦。

    因此,MybatisPlus 提供了一个处理枚举的类型转换器,可以帮我们把枚举类型与数据库类型自动转换。

    定义枚举

    我们定义一个用户状态的枚举:

    package com.itheima.mp.enums;
    
    import com.baomidou.mybatisplus.annotation.EnumValue;
    import lombok.Getter;
    
    @Getter
    public enum UserStatus {
        NORMAL(1, "正常"),
        FREEZE(2, "冻结")
        ;
        private final int value;
        private final String desc;
    
        UserStatus(int value, String desc) {
            this.value = value;
            this.desc = desc;
        }
    }
    

    项目结构如下:

    在这里插入图片描述

    然后把 User 类中和 UserVO 中的 status 字段改为 UserStatus 类型。

    将 UserServiceImpl 代码中的 2 替换为 UserStatus.FREEZE,显得更专业一点。

    要让 MybatisPlus 处理枚举与数据库类型自动转换,我们必须告诉 MybatisPlus,枚举中的哪个字段的值作为数据库值。

    MybatisPlus 中提供了 @EnumValue 注解来标记枚举属性:

    在这里插入图片描述

    配置枚举处理器

    在 application.yaml 文件中添加配置:

    mybatis-plus:
      configuration:
        default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
    

    然后我们测试一下查询:

    在这里插入图片描述

    查询成功,不过查询出的 User 类的 status 字段是枚举类型,可能不是很好理解。

    我们在 UserStatus 枚举中通过 @JsonValue 注解标记 JSON 序列化时要展示的字段,添加在 value 或者 desc 上都可以:

    比如,我们添加在 desc 上,就会显示 “正常” 或者 “冻结”:

    在这里插入图片描述

    在这里插入图片描述

    JSON处理器

    数据库的 user 表中有一个 info 字段,是 JSON 类型:

    在这里插入图片描述

    而目前我们的 User 实体类中却是 String 类型的:

    在这里插入图片描述

    这样一来,我们要读取 info 中的属性时就非常不方便。如果要方便获取,info 的类型最好是一个 Map 或者实体类。

    而一旦我们把 info 改为对象类型,就需要在写入数据库时手动转为 String,再读取数据库时,手动转换为对象,这将会非常麻烦。

    因此,MybatisPlus 为我们提供了很多特殊类型字段的类型处理器,解决特殊字段类型与数据库类型转换的问题。例如处理 JSON 就可以使用 JacksonTypeHandler 处理器。

    接下来,我们就来看看这个处理器该如何使用。

    定义实体

    首先,我们在 po 下定义一个单独实体类 UserInfo 来与 info 字段的属性匹配:

    package com.itheima.mp.domain.po;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor(staticName = "of")
    public class UserInfo {
        private Integer age;
        private String intro;
        private String gender;
    }
    

    使用类型处理器

    接下来,将 User 类的 info 字段修改为 UserInfo 类型,并声明类型处理器,同时开启结果自动映射;UserVO 中的 info 字段也需要修改为 UserInfo 类型:

    package com.itheima.mp.domain.po;
    
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
    import com.itheima.mp.enums.UserStatus;
    import lombok.Data;
    
    import java.time.LocalDateTime;
    
    @Data
    @TableName(value = "user", autoResultMap = true)    //开启结果自动映射
    public class User {
        @TableId(type = IdType.AUTO)    //不指定的话,默认为随机生成id,也就是第三种方式
        private Long id;                //用户id
    
        private String username;        //用户名
    
        private String password;        //密码
    
        private String phone;           //注册手机号
    
        @TableField(typeHandler = JacksonTypeHandler.class)
        private UserInfo info;            //详细信息
    
        private UserStatus status;      //使用状态(1正常 2冻结)
    
        private Integer balance;        //账户余额
    
        private LocalDateTime createTime;//创建时间
    
        private LocalDateTime updateTime;//更新时间
    }
    

    将测试方法中关于设置详细信息的代码修改一下:

    在这里插入图片描述

    重启服务,测试一下查询接口,可以看到信息成功返回:

    在这里插入图片描述

    插件功能

    MybatisPlus 提供了很多的插件功能,进一步拓展其功能。目前已有的插件有:

    • PaginationInnerInterceptor:自动分页
    • TenantLineInnerInterceptor:多租户
    • DynamicTableNameInnerInterceptor:动态表名
    • OptimisticLockerInnerInterceptor:乐观锁
    • IllegalSQLInnerInterceptor:sql 性能规范
    • BlockAttackInnerInterceptor:防止全表更新与删除

    最常用的是自动分页插件。

    分页插件

    在未引入分页插件的情况下,MybatisPlus 是不支持分页功能的,IService 和 BaseMapper 中的分页方法都无法正常起效。所以,我们必须配置分页插件。

    在项目中新建一个配置类,项目结构如下:

    在这里插入图片描述

    package com.itheima.mp.config;
    
    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() {
            // 1.初始化核心插件
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    
            // 2.创建分页插件
            PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
            paginationInnerInterceptor.setMaxLimit(1000L);  //设置最大分页限制
    
            // 3.添加分页插件
            interceptor.addInnerInterceptor(paginationInnerInterceptor);
    
            return interceptor;
        }
    }
    

    在 IUserServiceTest 中写一个分页查询的测试方法:

    @Test
    void testPageQuery() {
        // 1.准备分页条件
        int pageNum = 1, pageSize = 2;  //页码、每页大小
        Page<User> page = Page.of(pageNum, pageSize);
    
        // 2.排序条件
        page.addOrder(new OrderItem("balance", true));  // 先按余额升序排序
        page.addOrder(new OrderItem("id", true));       // 再按id升序排序
    
        // 3.分页查询
        Page<User> p = userService.page(page);
    
        // 4.解析数据
        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);
    }
    

    结果:

    在这里插入图片描述

    通用分页实体

    需求:遵循下面的接口规范,编写一个 UserController 接口,实现 User 的分页查询。

    在这里插入图片描述
    定义实体

    这里需要定义3个实体:

    • UserQuery:分页查询条件的实体,包含分页、排序参数、过滤条件
    • PageDTO:分页结果实体,包含总条数、总页数、当前页数据
    • UserVO:用户页面视图实体(已存在)

    虽然 UserQuery 之前已经定义过了,并且其中已经包含了过滤条件,其中缺少的仅仅是分页条件,但是分页条件不仅仅是用户分页查询需要,以后其它业务也都有分页查询的需求。因此建议将分页查询条件单独定义为一个 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 继承这个实体:

    package com.itheima.mp.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;
    }
    

    最后,则是分页实体 PageDTO,由于在其它微服务项目中可能也会使用到这个分页实体,我们将其定为为 DTO:

    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;
    }
    

    开发接口

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

    /**
     * 根据条件分页查询用户
     *
     * @param query
     * @return
     */
    @GetMapping("/page")
    @ApiOperation("根据条件分页查询用户接口")
    public PageDTO<UserVO> queryUsersPage(UserQuery query) {
        return userService.queryUsersPage(query);
    }
    

    在 IUserService 中创建 queryUsersPage 方法:

    /**
     * 根据条件分页查询用户
     *
     * @param query
     * @return
     */
    PageDTO<UserVO> queryUsersPage(UserQuery query);
    

    在 UserServiceImpl 中实现该方法:

    /**
     * 根据条件分页查询用户
     *
     * @param query
     * @return
     */
    public PageDTO<UserVO> queryUsersPage(UserQuery query) {
        String name = query.getName();
        Integer status = query.getStatus();
    
        // 构建分页条件
        Page<User> page = Page.of(query.getPageNo(), query.getPageSize()); 3
        
        // 排序条件
        if (StrUtil.isNotBlank(query.getSortBy())) {
            // 不为空
            page.addOrder(new OrderItem(query.getSortBy(), query.getIsAsc()));
        } else {
            // 为空,默认按照更新时间排序
            page.addOrder(new OrderItem("update_time", query.getIsAsc()));
        }
    
        // 分页查询
        Page<User> p = lambdaQuery()
                .like(name != null, User::getUsername, name)
                .eq(status != null, User::getStatus, status)
                .page(page);
    
        //封装VO结果
        PageDTO<UserVO> dto = new PageDTO<>();
        dto.setTotal(p.getTotal());
        dto.setPages(p.getPages());
    
        List<User> records = p.getRecords();
        if (CollUtil.isEmpty(records)) {
            // 为空,设置为空列表
            dto.setList(Collections.emptyList());
        } else {
            // 不为空
            dto.setList(BeanUtil.copyToList(records, UserVO.class));
        }
    
        return dto;
    }
    

    测试一下:

    在这里插入图片描述

    改造PageQuery实体

    在上面的代码中,从 PageQuery 到 MybatisPlus 的 Page 之间转换的过程还是比较麻烦的。

    我们完全可以在 PageQuery 这个实体中定义一个工具方法,来简化开发。

    package com.itheima.mp.domain.query;
    
    import cn.hutool.core.util.StrUtil;
    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;
    
    @Data
    @ApiModel(description = "分页查询实体")
    public class PageQuery {
        @ApiModelProperty("页码")
        private Long pageNo = 1L;
        @ApiModelProperty("每页数据条数")
        private Long pageSize = 2L;
        @ApiModelProperty("排序字段")
        private String sortBy;
        @ApiModelProperty("是否升序")
        private Boolean isAsc = true;
    
        // 多个参数
        public <T> Page<T> toMpPage(OrderItem... orders) {
            // 分页条件
            Page<T> page = Page.of(pageNo, pageSize);
    
            // 排序条件
            if (StrUtil.isNotBlank(sortBy)) {
                // 不为空
                page.addOrder(new OrderItem(sortBy, isAsc));
            } else if (orders != null) {
                // 为空
                page.addOrder(orders);
            }
    
            return page;
        }
    
        // 只有一个参数
        public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc) {
            return 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 的的转换:

    // 构建分页条件
    Page<User> page = query.toMpPageDefaultSortByUpdateTimeDesc();
    

    改造PageDTO实体

    同理,在查询出分页结果后,数据的非空校验,数据的 vo 转换都是模板代码,编写起来很麻烦。

    我们完全可以将其封装到 PageDTO 的工具方法中,简化整个过程:

    package com.itheima.mp.domain.dto;
    
    import cn.hutool.core.bean.BeanUtil;
    import cn.hutool.core.collection.CollUtil;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.itheima.mp.domain.po.User;
    import com.itheima.mp.domain.vo.UserVO;
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    import java.util.Collections;
    import java.util.List;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    
    @Data
    @ApiModel(description = "分页结果")
    public class PageDTO<T> {
        @ApiModelProperty("总条数")
        private Long total;
        @ApiModelProperty("总页数")
        private Long pages;
        @ApiModelProperty("集合")
        private List<T> list;
    
        // 需要传结果类型的字节码
        public static <PO, VO> PageDTO<VO> of(Page<PO> p, Class<VO> clazz) {
            PageDTO<VO> dto = new PageDTO<>();
            dto.setTotal(p.getTotal());     // 总条数
            dto.setPages(p.getPages());     // 总页数
    
            // 当前页数据
            List<PO> records = p.getRecords();
            if (CollUtil.isEmpty(records)) {
                // 为空,设置为空里欸博爱
                dto.setList(Collections.emptyList());
            } else {
                // 不为空
                dto.setList(BeanUtil.copyToList(records, clazz));
            }
    
            return dto;
        }
    
        // 需要传PO如何转换为VO的方法
        public static <PO, VO> PageDTO<VO> of(Page<PO> p, Function<PO, VO> convertor) {
            PageDTO<VO> dto = new PageDTO<>();
            dto.setTotal(p.getTotal());     // 总条数
            dto.setPages(p.getPages());     // 总页数
    
            // 当前页数据
            List<PO> records = p.getRecords();
            if (CollUtil.isEmpty(records)) {
                // 为空,设置为空里欸博爱
                dto.setList(Collections.emptyList());
            } else {
                // 不为空
                dto.setList(records.stream().map(convertor).collect(Collectors.toList()));
            }
    
            return dto;
        }
    }
    

    最终,业务层的代码可以简化为:

    public PageDTO<UserVO> queryUsersPage(UserQuery query) {
        String name = query.getName();
        Integer status = query.getStatus();
    
        // 构建分页条件
        Page<User> page = query.toMpPageDefaultSortByUpdateTimeDesc();
    
        // 分页查询
        Page<User> p = lambdaQuery()
                .like(name != null, User::getUsername, name)
                .eq(status != null, User::getStatus, status)
                .page(page);
    
        return PageDTO.of(p, UserVO.class);
    }
    

    如果是希望自定义 PO 到 VO 的转换过程,可以这样做:

    return PageDTO.of(p, user -> {
        // 1.拷贝基础属性
        UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
    
        // 2.处理特殊逻辑:比如用户名脱敏
        String username = vo.getUsername();
        vo.setUsername(username.substring(0, username.length() - 2) + "**");
    
        return vo;
    });
    

    测试一下:

    在这里插入图片描述

    在改进的过程中,我们都是在实体类中添加了对应的方法来简化我们的开发,这也意味着这些方法和实体类耦合了。因为我们使用的是 MP,所以可以这样写,如果使用的不是 MP,我们可以考虑定义一些工具类来实现这些类型之间的转换,以此让方法与实体类解耦合。

  • 相关阅读:
    聊聊RocketMQ中的broker的TPS和QPS为何相差巨大,是如何统计的
    kares中如何从已保存的训练模型中了解模型的具体结构
    跨域的解决方案
    Labview 前面板放置照片
    产品市场研究的方法有哪些
    跨越障碍:解决复杂网页数据提取的挑战
    计算机视觉入门-最小二乘法、随机取样法、鲁棒估计、霍夫变换
    数据同步工具Sqoop
    Python算法——树的最大深度和最小深度
    NC16466 [NOIP2015]信息传递 (带权并查集)
  • 原文地址:https://blog.csdn.net/weixin_62511863/article/details/139114721