【Springcloud微服务】MybatisPlus下篇

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

目录

  • 扩展功能
    • 代码生成
    • 静态工具
    • 逻辑删除
    • 枚举处理器
    • JSON处理器
  • 插件功能
    • 分页插件
    • 通用分页实体

扩展功能

代码生成

在使用 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,我们可以考虑定义一些工具类来实现这些类型之间的转换,以此让方法与实体类解耦合。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/678047.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Beyond Compare 4 代码对比重新激活使用30天

1.同时按住‘’Win”“R”键&#xff0c;打开运行窗口。 2.在文本框中输入“regedit”&#xff0c;然后点击“确定”。 3.打开注册表&#xff0c;删除相关注册信息 打开注册表后&#xff0c;依次点击左侧列的“HKEY_CURRENT_USER”-“SOFTWARE”-“Scooter Software”-“Beyo…

[预告] 现代C++之全面解读Mutex与RAII Lock

目标 在我们编写并发编程项目的时候&#xff0c;mutex是必须要掌握的点&#xff0c;深入mutex的底层原理与实现能够帮助我们更好的理解与使用mutex。例如&#xff1a;在编写代码时&#xff0c;我们会遇到如下几个场景&#xff1a; 如何避免死锁如何自动释放锁如何设置超时控制多…

KT142C语音芯片ic批量生产说明不需要usb拷贝音频

一、批量生产的简介 内置空间虚拟成U盘的批量生产说明&#xff0c;其实就是将音频文件配置文件打包成一个bin文件就可以了&#xff0c;当然借助于电脑端的exe工具。“FAT镜像文件生成工具_1.0.9.exe” 最后&#xff0c;将生成的文件&#xff0c;重命名为“userfat-日期-特点.b…

Digital Assets

目录 .HDA文件 Expanded directories .HDA文件 Houdini将数字资产存储于.hda文件内&#xff1b; .HDA文件格式是一种二进制存档格式&#xff08;binary archive format&#xff09;&#xff0c;存储一个或多个资产的数据层级结构&#xff0c;包括资产节点的类型定义&#xf…

配置本地 apt 源

挂载iso镜像文件 注意&#xff1a;文章中的挂载方法是临时挂载&#xff0c;重启服务器失效 我是使用iBMC的虚拟控制台将我的iso文件以设备的形式挂载到服务器上&#xff0c;我的iso文件是设备&#xff1a;/dev/sr0 也可以直接将iso文件上传到服务器某个目录。 将 /dev/sr0 进…

手把手制作Vue3+Flask全栈项目 全栈开发之路实战篇 问卷网站(二)管理员后台

全栈开发一条龙——前端篇 第一篇&#xff1a;框架确定、ide设置与项目创建 第二篇&#xff1a;介绍项目文件意义、组件结构与导入以及setup的引入。 第三篇&#xff1a;setup语法&#xff0c;设置响应式数据。 第四篇&#xff1a;数据绑定、计算属性和watch监视 第五篇 : 组件…

AI写作:AI助力内容创作,让你的工作效率翻倍

工欲善其事&#xff0c;必先利其器。 随着AI技术与各个行业或细分场景的深度融合&#xff0c;日常工作可使用的AI工具呈现出井喷式发展的趋势&#xff0c;AI工具的类别也从最初的AI文本生成、AI绘画工具&#xff0c;逐渐扩展到AI思维导图工具、AI流程图工具、AI生成PPT工具、AI…

海外仓出库系统:智能处理订单,增加海外仓货物流转率的关键所在

对海外仓来说&#xff0c;怎么才能提升效益&#xff1f;这应该是很多海外仓企业都在关心的问题。想提升海外仓的收益主要是三个大方向&#xff1a;开源、节流、提效。 所谓的开源&#xff0c;就是扩展业务类型和业务模式&#xff0c;在拓展新客户上下功夫。这是能让海外仓进来…

【C++软件调试技术】什么是pdb文件?如何使用pdb文件?哪些工具需要使用pdb文件?

目录 1、什么是pdb文件&#xff1f; 2、如何配置生成pdb文件&#xff1f; 3、pdb文件的时间戳和文件名称 3.1、pdb文件的时间戳 3.2、pdb文件的文件名称 4、有pdb文件才能在Visual Studio中调试代码 5、在Windbg中使用pdb文件 5.1、使用lm命令查看二进制文件的时间戳&a…

第九篇 有限状态机

实验九 有限状态机 9.1 实验目的 学习有限状态机的组成与类型&#xff1b; 掌握有限状态机的设计方式&#xff1b; 学习有限状态机的编码方式&#xff1b; 掌握使用有限状态机进行设计的方法。 9.2 原理介绍 9.2.1 有限状态机的基本概念 有限状态机&#xff08;Finite …

【TPAMI-2024】EfficientTrain++帮你降低网络训练的成本

写在前面&#xff1a;本博客仅作记录学习之用&#xff0c;部分图片来自网络&#xff0c;如需引用请注明出处&#xff0c;同时如有侵犯您的权益&#xff0c;请联系删除&#xff01; 文章目录 前言论文更容易学习的模式:频域易于学习的模式:空间域统一的训练课程 EFFICIENTTRAIN计…

计算机网络-BGP路由优选原则概述

前面我们已经学习了BGP的基础概念、对等体建立、报文类型等&#xff0c;也通过实践完成了IBGP和EBGP的实验配置&#xff0c;其实这些路由协议都是理论比较复杂&#xff0c;但是配置其实比较简单的&#xff0c;今天我们来学习BGP的路由优选原则。 一、IGP路由优选 前面我们学习了…

数据结构与算法-10_阻塞队列

文章目录 1.单锁实现2.双锁实现 1.单锁实现 Java 中防止代码段交错执行&#xff0c;有两种锁选择 synchronized 代码块&#xff0c;属于关键字级别提供锁保护&#xff0c;功能少ReentrantLock 类&#xff0c;功能丰富 以 ReentrantLock 为例 ReentrantLock lock new Reent…

tomcat-memcached会话共享配置

目录 1、安装memcache服务 2、把依赖的jar包移至tomcat/lib目录下 3、配置tomcat/conf/context.xml 4、重启tomcat服务 1、安装memcache服务 具体安装步骤此处不详细说明&#xff0c;自行根据实际情况安装即可 2、把依赖的jar包移至tomcat/lib目录下 3、配置tomcat/conf/c…

自定义类型:联合体和枚举

1. 联合体类型的声明 2. 联合体的特点 3. 联合体大小的计算 4. 枚举类型的声明 5. 枚举类型的优点 6. 枚举类型的使用 欢迎关注 熬夜学编程 创作不易&#xff0c;请多多支持 感谢大家的阅读、点赞、收藏和关注 如有问题&#xff0c;欢迎指正 1. 联合体 1.1 联合体类型的声…

java自学阶段一:基础知识学习

《项目案例—黑马tlias智能学习辅助系统》 目录&#xff1a; 异常 一&#xff1a;学习目标&#xff1a; 异常&#xff1a;能够看懂异常信息&#xff0c;了解异常体系结构和分类&#xff0c;掌握异常的两种处理方式&#xff0c;自定义异常。 二、异常&#xff1a; 1.异常的概…

yolo-v8window环境运行

源码https://github.com/ultralytics/ultralytics 1.用pycharm打开YOLOv8文件夹&#xff0c;下载依赖项&#xff0c;依赖项已经以作者的名字来封装好&#xff0c;所以直接在终端输入&#xff1a;pip install ultralytics&#xff0c;安装好之后会默认安装的cpu版本的torch&am…

WannaMine4.0病毒应急处置

一、前言 某日&#xff0c;通过流量监测设备和EDR发现挖矿请求告警&#xff0c;并存在长期445端口扫描。 二、病毒排查 上机排查&#xff0c;发现该服务器存在WannaMine4.0病毒&#xff0c;通过网上文章了解&#xff0c;如果请求挖矿域名遭安全设备拦截&#xff0c;会导致挖矿…

AI大模型页面

自己做的AI&#xff0c;模仿GPT。 访问地址&#xff1a;欢迎 请大家给点意见&#xff0c;需要追加哪些功能。

《企业应用架构模式》学习指南

导读&#xff1a;企业应用包括哪些&#xff1f;它们又分别有哪些架构模式&#xff1f; 世界著名软件开发大师Martin Fowler给你答案 01什么是企业应用 我的职业生涯专注于企业应用&#xff0c;因此&#xff0c;这里所谈及的模式也都是关于企业应用的。&#xff08;企业应用还有…