黑马微服务开发与实战学习笔记_MybatisPlus_P3 扩展功能

系列博客目录



Part1:代码生成器

  1. MyBatis-Plus官网有通过编写生成代码的代码来生成代码的方式,但是我本来就是不想写代码,现在还要写生成代码,显然不够好。
  2. 官网给出了MyBatisX的插件,但是不好用,可以自己试试,是通过鼠标右键点击来生成代码。
  3. 一个比较好用的插件MyBatisPlus,名字和MP就差了个横杠。
    在这里插入图片描述
    在这里插入图片描述
    Step1:首先点击Config Database,配置好要生成代码的数据库。
jdbc:mysql://localhost:3306/数据库名?serverTimezone=UTC

在这里插入图片描述
点击测试链接,显示测试成功后,点击OK。

Step2:之后点击Code Generator ,这里我们自己动手生成一下address表的代码。
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/3a9e253708f34f539d7d9ffe5154cf5d.png

module就是模块名,如果项目有模块的嵌入,就可以填写子模块名,也可以不填,默认生成在当前模块中。
package就是将我们生成的代码放在哪个包下。
over file就是生成代码的时候遇到相同的文件,是否覆盖。后面的是Id策略。
下面的比如是否生成实体类,如果生成,放在哪。
TablePrefix 就是有哪些表的前缀需要去掉,比如tb_user这个数据库表,可以通过填写tb_,来生产User的实体类。
可以通过下图中一个完整的体系结构感受一下上图填写的内容。
在这里插入图片描述
填写完毕后,可以点击代码生成(生产前不需先建好文件夹)。

Part2:DB静态工具

有的时候Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具类:Db,其中的一些静态方法与IService中方法签名基本一致,也可以帮助我们实现CRUD功能,如下图所示,但是这里面我们有时候需要不光传给他Id作为参数,还要传实体类的字节码,才能用反射拿到实体类的相关信息,从而得到注解上的类名,表名等来实现增删改查,比如右下方getById(),但是save()不用传字节码文件,因为你已经传了对象给他了。
在这里插入图片描述

案例

静态工具查询

需求:

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

上面的三个需求都需要查用户表和地址表,举例:第(1)个需求,我需要通过id查用户,所以我要查用户表,这时候我在用户业务层也就是UserService中进行操作,由于我还需要查询地址,需要用到AddressService中的功能,也就是我需要把AddressService注入到UserService中。其他需求中也可能会导致将UserService注入到AddressService中,这就可能会导致了循环依赖。第(3)个需求,我是要返回收货地址,但是返回之前我需要判断收货地址所属的用户是否正常,也就是我需要把UserService注入到AddressService中,这就可能会导致了循环依赖。虽然不注入Service,通过Mapper也行,但是Mapper的功能没有Service强大。

需求:改造根据id用户查询的接口,查询用户的同时返回用户收货地址列表。
首先,我们要添加一个收货地址的VO对象:

package com.itheima.mp.domain.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,添加一个地址属性:@ApiModelProperty("收货地址列表")private List<AddressVO> addresses;
接下来,修改UserController中根据id查询用户的业务接口:

@GetMapping("/{id}")
@ApiOperation("根据id查询用户")
public UserVO queryUserById(@PathVariable("id") Long userId){
    // 基于自定义service方法查询
    return userService.queryUserAndAddressById(userId);
}

由于查询业务复杂,所以要在service层来实现。首先在IUserService中定义方法:

package com.itheima.mp.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.vo.UserVO;

public interface IUserService extends IService<User> {
    UserVO queryUserAndAddressById(Long userId);
}

然后,在UserServiceImpl中实现该方法:

@Override
public UserVO queryUserAndAddressById(Long userId) {
    // 1.查询用户
    User user = getById(userId);
    if (user == null) {
        return null;
    }
    // 2.查询收货地址
    List<Address> addresses = Db.lambdaQuery(Address.class)//使用DB
            .eq(Address::getUserId, userId)
            .list();
    // 3.处理vo
    UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
    userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));
    return userVO;
}

在查询地址时,我们采用了Db的静态方法,因此避免了注入AddressService(直接在UserServiceImpl中声明AddressService,然后调用其函数),减少了循环依赖的风险。

练习

Part3:逻辑删除

逻辑删除 是一种常见的数据库操作方式,它通过改变数据的状态(通常是某个字段的值)来表示该数据已被删除,而不是直接从数据库中物理删除。这种方法可以保留数据记录,便于后续恢复、审计和历史数据追踪。
举例:比如外卖中,删除订单按钮,用户点击删除,可能因为不喜欢买的外卖,其实不会真的被删除,商家是需要根据用户的订单进行统计分析的。

对于一些比较重要的数据,我们往往会采用逻辑删除的方案,即:

  • 在表中添加一个字段标记数据是否被删除
  • 当删除数据时把标记置为true
  • 查询时过滤掉标记为true的数据

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

例如
逻辑删除: UPDATE user SET deleted=1 WHERE id= 1 AND deleted =0 后面这个 deleted = 0 是防止重复操作,必须要没删的数据才能被删,不能是已经删除过的。
查询操作: SELECT * FROM user WHERE deleted = 0
更新操作也要改,因为被逻辑删除了的数据没必要更新。

这样所有之前的Sql语句都要修改?为了解决这个问题,MybatisPlus就添加了对逻辑删除的支持。

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

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

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)

实战

给address表添加一个逻辑删除字段:

alter table address add deleted bit default b'0' null comment '逻辑删除';

然后给Address实体添加deleted字段:
在这里插入图片描述
测试:
首先,我们执行一个删除操作:

@Test
void testDeleteByLogic() {
    // 删除方法与以前没有区别
    addressService.removeById(59L);
}

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

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

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

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

Part4:枚举处理器

User类中有一个用户状态字段:如果很多状态,我们容易记不住哪个对应那个。解决方法:如果状态是有限数量的,就可以用枚举,对应哪个数字对应哪个状态就一目了然了。
在这里插入图片描述

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

在这里插入图片描述
到现在为止我们对status赋值的时候,还是要赋值1或者2这种数字,这样存在两个问题:
(1)每次赋值我得来查一下枚举,正常对应哪个数字。(2)而且代码中还是只有status为1或者2,并不是直接显示为正常或者其他状态。对于没有接触过这个业务的人,会很懵。

然后把User类中的status字段改为UserStatus 类型:这时候就不会再有数字了,而是英文单词。
在这里插入图片描述

但是我们数据库采用的是int类型,对应的PO(Persistent Object 持久化对象)已经改成了枚举类型。如何实现数据库中的int转化成枚举类型呢?MybatisPlus提供了一个处理枚举的类型转换器,可以帮我们把枚举类型数据库类型自动转换
要让MybatisPlus处理枚举与数据库类型自动转换,我们必须告诉MybatisPlus,枚举中的哪个字段的值作为数据库值。MybatisPlus提供了@EnumValue注解来标记枚举属性value:MP就知道把value往数据库中写。
在这里插入图片描述
还需要(通过在application.yaml文件中添加配置)配置枚举处理器,在application.yaml文件中添加配置:

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

测试

@Test
void testService() {
    List<User> list = userService.list();
    list.forEach(System.out::println);
}

最终,查询出的User类的status字段会是枚举类型:
在这里插入图片描述
同时,为了使页面查询结果也是枚举格式,我们需要修改UserVO中的status属性:
在这里插入图片描述
并且,在UserStatus枚举中通过@JsonValue注解标记JSON序列化时遇到枚举类型应该展示哪个字段:
在这里插入图片描述
最后,在页面查询,结果如下:

在这里插入图片描述

Part5:JSON类型处理器

数据库的user表中有一个info字段,是JSON类型:
在这里插入图片描述
格式像这样:

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

而目前User实体类中却是String类型:
在这里插入图片描述
这样一来,我们要读取info中的属性时就非常不方便。如果要方便获取,info的类型最好是一个Map或者实体类

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

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

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

Step1 定义实体

首先,我们定义一个单独实体类来与info字段的属性匹配。
在这里插入图片描述

package com.itheima.mp.domain.po;

import lombok.Data;

@Data
public class UserInfo {
    private Integer age;
    private String intro;
    private String gender;
}

Step2 使用类型处理器

接下来,将User类的info字段修改为UserInfo类型,并声明类型处理器,像这种对象嵌套的话,往往还要定义复杂的resultMap ,但我们不想定义,所以还要在函数上面添加注释实现自动化。
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/9653287d314a47bbab17a1b6b69cbd6c.png
)
测试可以发现,所有数据都正确封装到UserInfo当中了:
在这里插入图片描述
同时,为了让页面返回的结果也以对象格式返回,我们要修改UserVO中的info字段:
在这里插入图片描述
此时,在页面查询结果如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值