微服务-MybatisPlus下
创始人
2024-11-17 13:35:42
0

微服务-MybatisPlus下

文章目录

  • 微服务-MybatisPlus下
    • 1 MybatisPlus扩展功能
      • 1.1 代码生成
      • 1.2 静态工具
      • 1.3 逻辑删除
      • 1.4 枚举处理器
      • 1.5 JSON处理器
        • **1.5.1.定义实体**
        • **1.5.2.使用类型处理器**
      • **1.6 配置加密(选学)**
        • 1.6.1.生成秘钥
        • **1.6.2.修改配置**
        • **1.6.3.测试**
    • 2 插件功能
      • 2.1 分页插件
      • 2.2 通用分页实体
        • 2.2.1 简单分页查询
        • 2.2.2 通用分页查询

1 MybatisPlus扩展功能

1.1 代码生成

自己从头开始,则步骤如下图所示


在这里插入图片描述

在这里插入图片描述

1.2 静态工具

有的时候Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具类:Db,其中的一些静态方法IService中方法 签名基本一致。主要差别就是由于是静态的,之前没有指定过实体对象,所以除save、update以外的函数几乎都要传入实体类,让底层调用反射机制,得到实体类。而save、update由于本身参数就需要传入一个实体类对象,此对象就可通过反射得到实体类,所以不需要再传入实体类。

实现CRUD功能:

案例:静态工具查询

需求:

  1. 改造根据id查询用户的接口,查询用户的同时,查询出用户对应的所有地址
  2. 改造根据id批量查询用户的接口,查询用户的同时,查询出用户对应的所有地址
  3. 实现根据用户id查询收货地址功能,需要验证用户状态,冻结用户抛出异常(练习)

可以看到以上需求一般都需要在注入了userService之后,再注入addressService才能实现(当然你用多表联合或者注入mapper当我没说)。那这就涉及到了多个Service的相互调用,出现循环依赖问题,这种情况下就可以使用静态工具来解决。而且静态工具不需要注入,这样就避免了循环依赖

第一题:

  • controller层
    @GetMapping("{id}")     @ApiOperation(value = "根据id查询用户接口")     public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id){ //        User user = userService.getById(id); //        UserVO userVO = BeanUtil.copyProperties(user, UserVO.class); //        return userVO;         return userService.queryUserAndAddressById(id);     } 
  • service层
    @Override     public UserVO queryUserAndAddressById(Long id) {          //1. 查询用户         User user = getById(id);         if(user == null || user.getStatus() == 2){             throw new RuntimeException("用户状态异常");         }         //2. 查询地址         //这里本来可以直接用本service的lambdaQuery函数 单位了避免循环依赖,就使用静态工具         List
list = 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(list)){ userVO.setAddressList(BeanUtil.copyToList(list, AddressVO.class)); } return userVO; }

第二题:

    @Override     public List queryUserAndAddressByIds(List ids) {         //1. 查询用户         List users = listByIds(ids);         if(CollUtil.isEmpty(users)){             return Collections.emptyList();         }         //2. 查询地址         //2.1 获取用户id集合         List userIds = users.stream().map(User::getId).collect(Collectors.toList());         //2.2 根据用户id查询地址         List
addresses = Db.lambdaQuery(Address.class).in(Address::getUserId, userIds).list(); //2.3 转换地址VO List addressVOList = BeanUtil.copyToList(addresses, AddressVO.class); //2.4 用户地址集合分组处理、相同用户的放入一个集合(组)中 Map> addressMap = new HashMap<>(0); if(CollUtil.isNotEmpty(addressVOList)) { addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId)); } //3. 转换为VO返回 List list = new ArrayList<>(users.size()); for(User user:users){ //3.1 转换user的PO为VO UserVO userVO = BeanUtil.copyProperties(user, UserVO.class); list.add(userVO); //3.2 转换为VO userVO.setAddressList(addressMap.get(user.getId())); } return list; }

也就是静态工具的使用和IService一样,只不过多了个实体类传入。当一个serivice中需要多个service时,就是用静态工具,用法类似,也有普通函数以及复杂条件查询函数,比如Db.lambdaQuery,Db.Db.lambdaUpdate。

1.3 逻辑删除

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

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

在这里插入图片描述

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

在这里插入图片描述

    @Autowired     protected AddressService addressService;      @Test     void testLogicDelete(){         //1. 删除         addressService.removeById(59L);          //2. 查询         Address address = addressService.getById(59L);         System.out.println("address = " + address);     }  

虽然使用的remove方法,但是实际执行的是update方法。且在数据库中还存在,只不过deleted变成了true

在这里插入图片描述

1.4 枚举处理器

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

在这里插入图片描述

使用枚举表示状态,可以提高可读性。但是问题来了

在这里插入图片描述

数据库中status仍然是int类型,这就需要一个枚举类型到int类型的转化。

在这里插入图片描述

幸运的是Mybatis和mp帮我们解决了,如上图所示。只需要在枚举类中对应的字段上加上@EnumValue注释 mp就会自动把对应的字段值与数据库中列匹配

在这里插入图片描述

在applicaiton.yml中配置全局枚举类处理器

在这里插入图片描述

@JsonValue //用于枚举的返回值 数据库返回显示的值,比如此处是返回desc的值 也可以返回value的值 否则默认返回的是枚举的对象名 比如此处的NORMAL FROZEN

@Getter public enum UserStatus {     NORMAL(1,"正常"),     FROZEN(2,"冻结"),     ;      @EnumValue     private final int value;     @JsonValue  //用于枚举的返回值 数据库返回显示的值     private final String desc;      UserStatus(int value,String desc){         this.value = value;         this.desc = desc;     }  } 

在这里插入图片描述

1.5 JSON处理器

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

在这里插入图片描述

格式像这样:

{"age": 20, "intro": "佛系青年", "gender": "male"} 

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

在这里插入图片描述

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

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

意思就是对象的String字段可以被mp自动转换为数据库中的JSON,但是数据库中的JSON不能被mp自动帮我们转换为String或者相应的对象

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

在这里插入图片描述

1.5.1.定义实体

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

在这里插入图片描述

代码如下:

package com.itheima.mp.domain.po;  import lombok.Data;  @Data public class UserInfo {     private Integer age;     private String intro;     private String gender; } 
1.5.2.使用类型处理器

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

在这里插入图片描述

测试可以发现,所有数据都正确封装到UserInfo当中了:

在这里插入图片描述

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

在这里插入图片描述

此时,在页面查询结果如下:

在这里插入图片描述

注意: 由于User类中嵌套了UserInfo类,出现了类的嵌套,那么就需要定义复杂的ResultMap,但是我们又不想定义,那么就直接在User类上加注释,autoResultMap = true 如下图所示

在这里插入图片描述

1.6 配置加密(选学)

目前我们配置文件中的很多参数都是明文,如果开发人员发生流动,很容易导致敏感信息的泄露。所以MybatisPlus支持配置文件的加密和解密功能。

我们以数据库的用户名和密码为例。

1.6.1.生成秘钥

首先,我们利用AES工具生成一个随机秘钥,然后对用户名、密码加密:

package com.itheima.mp;  import com.baomidou.mybatisplus.core.toolkit.AES; import org.junit.jupiter.api.Test;  class MpDemoApplicationTests {     @Test     void contextLoads() {         // 生成 16 位随机 AES 密钥         String randomKey = AES.generateRandomKey();         System.out.println("randomKey = " + randomKey);          // 利用密钥对用户名加密         String username = AES.encrypt("root", randomKey);         System.out.println("username = " + username);          // 利用密钥对用户名加密         String password = AES.encrypt("MySQL123", randomKey);         System.out.println("password = " + password);      } } 

打印结果如下:

randomKey = 6234633a66fb399f username = px2bAbnUfiY8K/IgsKvscg== password = FGvCSEaOuga3ulDAsxw68Q== 
1.6.2.修改配置

修改application.yaml文件,把jdbc的用户名、密码修改为刚刚加密生成的密文:

spring:   datasource:     url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true     driver-class-name: com.mysql.cj.jdbc.Driver     username: mpw:QWWVnk1Oal3258x5rVhaeQ== # 密文要以 mpw:开头     password: mpw:EUFmeH3cNAzdRGdOQcabWg== # 密文要以 mpw:开头 
1.6.3.测试

在启动项目的时候,需要把刚才生成的秘钥添加到启动参数中,像这样:

–mpw.key=6234633a66fb399f

单元测试的时候不能添加启动参数,所以要在测试类的注解上配置:

在这里插入图片描述

然后随意运行一个单元测试,可以发现数据库查询正常。

2 插件功能

MyBatisPlus提供的内置拦截器有下面这些

2.1 分页插件

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

  • 首先,要在配置类中注册MyBatisPlus的核心插件,同时添加分页插件

  • 接着,就可以使用分页的API了

在这里插入图片描述

在这里插入图片描述

    void testPageQuery(){         int pageNo = 1;         int pageSize = 2;         //1. 准备分页条件         //1.1 分页条件         Page page = Page.of(pageNo, pageSize);         //1.2 排序条件  可以指定多个 比如下面 余额相同则按id排序         page.addOrder(new OrderItem("balance",true));         page.addOrder(new OrderItem("id",true));         //2. 分页查询         Page p = userService.page(page);          //3. 解析         long total = p.getTotal();  //总条数         System.out.println("total = " + total);         long pages = p.getPages();  //总页数         System.out.println("pages = " + pages);         List records = p.getRecords();    //分页数据         records.forEach(System.out::println);     } 

2.2 通用分页实体

2.2.1 简单分页查询

案例:简单分页查询案例

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

在这里插入图片描述

  • 实体类
public class PageQuery {     @ApiModelProperty("页码")     private Integer pageNo;     @ApiModelProperty("页大小")     private Integer pageSize;     @ApiModelProperty("排序字段")     private String sortBy;     @ApiModelProperty("是否升序")     private Boolean isAsc; }  @Data @EqualsAndHashCode(callSuper = true) @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; } 
  • controller
    @GetMapping("/page")     @ApiOperation(value = "根据条件分页查询用户接口")     public PageDTO queryUsersPage(UserQuery query){         return userService.queryUsersPage(query);     } 
  • impl层
    @Override     public PageDTO queryUsersPage(UserQuery query) {         String name = query.getName();         Integer status = query.getStatus();         //1. 构建查询条件         Page page = Page.of(query.getPageNo(), query.getPageSize());         if(StrUtil.isNotBlank(query.getSortBy())){             //不为空             page.addOrder(new OrderItem(query.getSortBy(),query.getIsAsc()));         }else {             //为空 默认按照更新时间排序             page.addOrder(new OrderItem("update_time",false));         }         //2. 分页查询         Page 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 userVOPageDTO = new PageDTO<>();         userVOPageDTO.setTotal(p.getTotal());         userVOPageDTO.setPages(p.getPages());         List records = p.getRecords();         if(CollUtil.isEmpty(records)){             userVOPageDTO.setList(Collections.emptyList());         }else {             List userVOS = BeanUtil.copyToList(records, UserVO.class);             userVOPageDTO.setList(userVOS);         }         //4. 返回         return userVOPageDTO;     } 
2.2.2 通用分页查询

在这里插入图片描述

  • 改造PageQuery类
@Data @ApiModel(description = "分页查询实体") public class PageQuery {     @ApiModelProperty("页码")     private Integer pageNo = 1;     @ApiModelProperty("页大小")     private Integer pageSize = 5;     @ApiModelProperty("排序字段")     private String sortBy;     @ApiModelProperty("是否升序")     private Boolean isAsc = true;      public   Page toMpPage(OrderItem ... orders){         // 1.分页条件         Page 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  Page toMpPage(String defaultSortBy, boolean isAsc){         return this.toMpPage(new OrderItem(defaultSortBy, isAsc));     }      public  Page toMpPageDefaultSortByCreateTimeDesc() {         return toMpPage("create_time", false);     }      public  Page toMpPageDefaultSortByUpdateTimeDesc() {         return toMpPage("update_time", false);     } } 
  • 改造PageDTO类
@Data @ApiModel(description = "分页结果") @AllArgsConstructor @NoArgsConstructor public class PageDTO {      @ApiModelProperty("总条数")     private Long total;     @ApiModelProperty("总页数")     private Long pages;     @ApiModelProperty("总数据")     private List list;      /**      * 返回空分页结果      * @param p MybatisPlus的分页结果      * @param  目标VO类型      * @param 

原始PO类型 * @return VO的分页对象 */ public static PageDTO empty(Page

p){ return new PageDTO<>(p.getTotal(), p.getPages(), Collections.emptyList()); } /** * 将MybatisPlus分页结果转为 VO分页结果 * @param p MybatisPlus的分页结果 * @param voClass 目标VO类型的字节码 * @param 目标VO类型 * @param

原始PO类型 * @return VO的分页对象 */ public static PageDTO of(Page

p, Class voClass) { // 1.非空校验 List

records = p.getRecords(); if (records == null || records.size() <= 0) { // 无数据,返回空结果 return empty(p); } // 2.数据转换 List 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 目标VO类型 * @param

原始PO类型 * @return VO的分页对象 */ public static PageDTO of(Page

p, Function convertor) { // 1.非空校验 List

records = p.getRecords(); if (records == null || records.size() <= 0) { // 无数据,返回空结果 return empty(p); } // 2.数据转换 List vos = records.stream().map(convertor).collect(Collectors.toList()); // 3.封装返回 return new PageDTO<>(p.getTotal(), p.getPages(), vos); } }

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

相关内容

热门资讯

什么软件防勒索 防勒索软件是一种专门设计用来防止勒索软件攻击的计算机安全工具。常见的防勒索软件有:Kaspersky...
咕咕语音怎么签到-咕咕语音签到... 咕咕语音的签到功能通常在应用的主界面或活动页面中,您可以查找带有签到图标或文字的部分进行签到操作。具...
ipad第7代参数配置详细 iPad 7拥有10.2英寸Retina显示屏,搭载A10 Fusion芯片,提供32GB和128G...
安兔兔跑分排行榜(手机最新的性... 安兔兔跑分排行榜是根据手机性能测试软件安兔兔的测试结果,对手机进行性能排名的一个榜单,可以作为参考了...
苹果ipad怎么外接u盘在哪里 苹果iPad可以通过使用带有Lightning接口的USB适配器来外接U盘。将U盘插入适配器,然后将...
win10哪个版本最流畅稳定(... Win10中,以20H2(即19042.572)版本较为流畅稳定,它集成了之前版本的功能和优化,同时...
2023即将发布的新手机(旗舰... 2023年新旗舰手机基础顶配:超高清AMOLED显示屏,支持120Hz刷新率;搭载最新高性能处理器;...
充电器口的三种型号(手机充电接... 手机充电接口主要有Micro USB, USB Type-C和Lightning三种类型。Micro...
乐感浏览器怎么设置增强播放器-... 在乐感浏览器中,打开设置菜单,选择“增强播放器”选项,根据需求调整音频、视频播放效果,最后点击“保存...
wps office怎么做表格... 在WPS Office中,选择需要添加分割线的单元格,然后点击工具栏上的“边框”按钮,选择“更多边框...